view/js/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js   F
last analyzed

Complexity

Total Complexity 3023
Complexity/F 2.51

Size

Lines of Code 14648
Function Count 1204

Duplication

Duplicated Lines 328
Ratio 2.24 %

Importance

Changes 0
Metric Value
wmc 3023
eloc 8364
c 0
b 0
f 0
dl 328
loc 14648
rs 0.8
mnd 1819
bc 1819
fnc 1204
bpm 1.5107
cpm 2.5107
noi 489

210 Functions

Rating   Name   Duplication   Size   Complexity  
A bootstrap3-wysihtml5.all.js ➔ assertSameDocumentOrFragment 0 5 2
A bootstrap3-wysihtml5.all.js ➔ SelectionPrototype 0 1 1
A bootstrap3-wysihtml5.all.js ➔ bindSideclick 0 12 3
A bootstrap3-wysihtml5.all.js ➔ _hasClassName 0 7 2
A bootstrap3-wysihtml5.all.js ➔ isMatchingAllready 0 9 3
C bootstrap3-wysihtml5.all.js ➔ _removeFormat 0 21 9
F bootstrap3-wysihtml5.all.js ➔ comparePoints 0 38 15
A bootstrap3-wysihtml5.all.js ➔ rangeContainsSingleElement 0 11 4
A bootstrap3-wysihtml5.all.js ➔ _changeLinks 0 19 5
A bootstrap3-wysihtml5.all.js ➔ fragmentFromNodeChildren 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _isLineBreak 0 3 1
A bootstrap3-wysihtml5.all.js ➔ createRangeContentRemover 0 32 3
A bootstrap3-wysihtml5.all.js ➔ assertNode 0 5 2
A bootstrap3-wysihtml5.all.js ➔ getSingleElementFromRange 0 7 2
A bootstrap3-wysihtml5.all.js ➔ hasStyleAttr 0 7 3
A bootstrap3-wysihtml5.all.js ➔ removeElement 0 3 1
B bootstrap3-wysihtml5.all.js ➔ _handleStyles 0 20 8
A bootstrap3-wysihtml5.all.js ➔ parentElement 0 4 2
A bootstrap3-wysihtml5.all.js ➔ createMultiplePropertyTest 0 11 4
A bootstrap3-wysihtml5.all.js ➔ _getApplier 0 16 4
F bootstrap3-wysihtml5.all.js ➔ cleanTempElements 0 6 176
A bootstrap3-wysihtml5.all.js ➔ getBoundaryBeforeNode 0 3 1
F bootstrap3-wysihtml5.all.js ➔ createAncestorFinder 0 13 25
B bootstrap3-wysihtml5.all.js ➔ updateRangeProperties 0 9 6
A bootstrap3-wysihtml5.all.js ➔ updateBoundaries 0 9 1
A bootstrap3-wysihtml5.all.js ➔ _getAttribute 0 20 4
B bootstrap3-wysihtml5.all.js ➔ _execCommand 0 26 7
A bootstrap3-wysihtml5.all.js ➔ splitDataNode 0 22 5
A bootstrap3-wysihtml5.all.js ➔ copyComparisonConstants 0 4 1
F bootstrap3-wysihtml5.all.js ➔ queryInList 0 11 95
A bootstrap3-wysihtml5.all.js ➔ getClosestAncestorIn 0 11 4
B bootstrap3-wysihtml5.all.js ➔ getNodesInRange 0 31 7
F bootstrap3-wysihtml5.all.js ➔ _getTagNames 0 4 35
A bootstrap3-wysihtml5.all.js ➔ _selectionWrap 0 14 3
F bootstrap3-wysihtml5.all.js ➔ _isSameNodeName 0 11 45
A bootstrap3-wysihtml5.all.js ➔ _createList 0 3 1
D bootstrap3-wysihtml5.all.js ➔ createModule 0 15 12
A bootstrap3-wysihtml5.all.js ➔ getNativeRange 0 13 4
D bootstrap3-wysihtml5.all.js ➔ gEBI 0 3 12
B bootstrap3-wysihtml5.all.js ➔ WrappedSelection 0 7 7
F bootstrap3-wysihtml5.all.js ➔ parse 0 44 26
A bootstrap3-wysihtml5.all.js ➔ getCommonAncestor 0 14 4
A bootstrap3-wysihtml5.all.js ➔ DOMException 0 5 4
A bootstrap3-wysihtml5.all.js ➔ _hasParentThatShouldBeIgnored 0 16 5
A bootstrap3-wysihtml5.all.js ➔ shim 0 9 5
A bootstrap3-wysihtml5.all.js ➔ isNonTextPartiallySelected 0 4 3
B bootstrap3-wysihtml5.all.js ➔ elementsHaveSameNonClassAttributes 0 19 6
A bootstrap3-wysihtml5.all.js ➔ handleMouseUp 0 8 2
A bootstrap3-wysihtml5.all.js ➔ getRootContainer 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _wrapMatchesInNode 0 16 2
A bootstrap3-wysihtml5.all.js ➔ fail 0 5 1
A bootstrap3-wysihtml5.all.js ➔ program13 0 5 1
A bootstrap3-wysihtml5.all.js ➔ program17 0 5 1
A bootstrap3-wysihtml5.all.js ➔ program7 0 5 1
A bootstrap3-wysihtml5.all.js ➔ removeStyle 0 17 5
A bootstrap3-wysihtml5.all.js ➔ getNodeIndex 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _appendLineBreak 0 4 1
F bootstrap3-wysihtml5.all.js ➔ program1 0 7 29
A bootstrap3-wysihtml5.all.js ➔ h 0 1 3
A bootstrap3-wysihtml5.all.js ➔ deleteSubtree 0 12 3
F bootstrap3-wysihtml5.all.js ➔ _handleAttributes 0 135 30
A bootstrap3-wysihtml5.all.js ➔ isTextOrCommentNode 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _convertUrlsToLinks 0 23 5
A bootstrap3-wysihtml5.all.js ➔ isAncestorOf 0 11 4
A bootstrap3-wysihtml5.all.js ➔ _isLoadedImage 0 9 3
A bootstrap3-wysihtml5.all.js ➔ addClass 0 8 2
A bootstrap3-wysihtml5.all.js ➔ removeClass 0 5 2
A bootstrap3-wysihtml5.all.js ➔ program15 0 5 1
A bootstrap3-wysihtml5.all.js ➔ removeMarkerElement 0 6 2
A bootstrap3-wysihtml5.all.js ➔ insertAfter 0 9 1
A bootstrap3-wysihtml5.all.js ➔ isHostProperty 0 3 1
A bootstrap3-wysihtml5.all.js ➔ program9 0 5 1
F bootstrap3-wysihtml5.all.js ➔ getErrorDesc 0 3 18
A bootstrap3-wysihtml5.all.js ➔ _isElement 0 3 1
A bootstrap3-wysihtml5.all.js ➔ isBrokenNode 0 9 2
C bootstrap3-wysihtml5.all.js ➔ e 0 1 10
A bootstrap3-wysihtml5.all.js ➔ expandRangeToSurround 0 19 4
A bootstrap3-wysihtml5.all.js ➔ _parseNode 0 24 5
A bootstrap3-wysihtml5.all.js ➔ isTextRange 0 3 1
C bootstrap3-wysihtml5.all.js ➔ isIE 0 22 11
C bootstrap3-wysihtml5.all.js ➔ convertToList 0 66 11
A bootstrap3-wysihtml5.all.js ➔ getIframeWindow 0 9 3
B bootstrap3-wysihtml5.all.js ➔ actOnCachedSelection 0 21 6
C bootstrap3-wysihtml5.all.js ➔ splitNodeAt 0 30 9
C bootstrap3-wysihtml5.all.js ➔ isHostMethod 0 4 11
A bootstrap3-wysihtml5.all.js ➔ assertValidOffset 0 5 3
B bootstrap3-wysihtml5.all.js ➔ createControlSelection 0 17 8
C bootstrap3-wysihtml5.all.js ➔ resolveList 0 54 11
A bootstrap3-wysihtml5.all.js ➔ getBody 0 3 2
A bootstrap3-wysihtml5.all.js ➔ isCharacterDataNode 0 4 1
A bootstrap3-wysihtml5.all.js ➔ assertNodeNotReadOnly 0 5 2
A bootstrap3-wysihtml5.all.js ➔ addSelections 0 5 2
A bootstrap3-wysihtml5.all.js ➔ b 0 1 3
F bootstrap3-wysihtml5.all.js ➔ _format 0 43 110
A bootstrap3-wysihtml5.all.js ➔ c 0 1 2
A bootstrap3-wysihtml5.all.js ➔ getRangeDocument 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _testTypes 0 18 5
F bootstrap3-wysihtml5.all.js ➔ autoLink 0 11 89
A bootstrap3-wysihtml5.all.js ➔ getDocSelection 0 3 1
B bootstrap3-wysihtml5.all.js ➔ insertNodeAtPosition 0 15 6
B bootstrap3-wysihtml5.all.js ➔ iterateSubtree 0 31 7
A bootstrap3-wysihtml5.all.js ➔ areMatchingAllready 0 8 4
B bootstrap3-wysihtml5.all.js ➔ createIterator 0 3 7
A bootstrap3-wysihtml5.all.js ➔ _hasStyles 0 3 1
F bootstrap3-wysihtml5.all.js ➔ copyComparisonConstantsToObject 0 11 67
A bootstrap3-wysihtml5.all.js ➔ updateControlSelection 0 25 4
F bootstrap3-wysihtml5.all.js ➔ _createListItem 0 5 16
A bootstrap3-wysihtml5.all.js ➔ _getTempElement 0 7 2
A bootstrap3-wysihtml5.all.js ➔ saveSelection 0 24 3
A bootstrap3-wysihtml5.all.js ➔ saveRange 0 26 3
F bootstrap3-wysihtml5.all.js ➔ adjust 0 12 132
B bootstrap3-wysihtml5.all.js ➔ getContentDocument 0 24 6
A bootstrap3-wysihtml5.all.js ➔ RangePrototype 0 1 5
A bootstrap3-wysihtml5.all.js ➔ androidVersion 0 3 1
A bootstrap3-wysihtml5.all.js ➔ getBoundaryAfterNode 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _addStyle 0 8 2
A bootstrap3-wysihtml5.all.js ➔ selectCells 0 9 1
A bootstrap3-wysihtml5.all.js ➔ _hasClasses 0 3 1
A bootstrap3-wysihtml5.all.js ➔ consoleLog 0 5 3
A bootstrap3-wysihtml5.all.js ➔ getIframeDocument 0 9 3
A bootstrap3-wysihtml5.all.js ➔ getDepth 0 10 3
F bootstrap3-wysihtml5.all.js ➔ _testType 0 63 19
A bootstrap3-wysihtml5.all.js ➔ deleteProperties 0 5 1
A bootstrap3-wysihtml5.all.js ➔ cloneSubtree 0 18 4
A bootstrap3-wysihtml5.all.js ➔ array_contains 0 9 3
C bootstrap3-wysihtml5.all.js ➔ HTMLApplier 0 10 10
A bootstrap3-wysihtml5.all.js ➔ updateFromTextRange 0 9 1
A bootstrap3-wysihtml5.all.js ➔ isHostObject 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _removeStyle 0 8 2
A bootstrap3-wysihtml5.all.js ➔ updateAnchorAndFocusFromNativeSelection 0 7 1
A bootstrap3-wysihtml5.all.js ➔ assertRangeValid 0 5 2
A bootstrap3-wysihtml5.all.js ➔ replaceWithOwnChildren 0 7 2
F bootstrap3-wysihtml5.all.js ➔ init 0 58 16
A bootstrap3-wysihtml5.all.js ➔ hasSameClasses 0 3 1
A bootstrap3-wysihtml5.all.js ➔ getNodeLength 0 12 2
A bootstrap3-wysihtml5.all.js ➔ removeOrChangeStyle 0 13 2
F bootstrap3-wysihtml5.all.js ➔ isHtmlNamespace 0 4 16
A bootstrap3-wysihtml5.all.js ➔ DomPosition 0 4 1
A bootstrap3-wysihtml5.all.js ➔ RangeIterator 0 23 5
A bootstrap3-wysihtml5.all.js ➔ g 0 1 2
A bootstrap3-wysihtml5.all.js ➔ isOrIsAncestorOf 0 3 1
F bootstrap3-wysihtml5.all.js ➔ _isBlockElement 0 3 46
A bootstrap3-wysihtml5.all.js ➔ NodeIterator 0 4 5
A bootstrap3-wysihtml5.all.js ➔ _getDocumentIdentifier 0 3 4
A bootstrap3-wysihtml5.all.js ➔ addStyle 0 12 3
A bootstrap3-wysihtml5.all.js ➔ _handleText 0 11 2
A bootstrap3-wysihtml5.all.js ➔ assertValidNodeType 0 5 2
B bootstrap3-wysihtml5.all.js ➔ a 0 1 7
A bootstrap3-wysihtml5.all.js ➔ restoreRanges 0 13 2
F bootstrap3-wysihtml5.all.js ➔ updateAnchorAndFocusFromRange 0 7 24
A bootstrap3-wysihtml5.all.js ➔ extractSubtree 0 18 4
A bootstrap3-wysihtml5.all.js ➔ isSplitPoint 0 13 4
F bootstrap3-wysihtml5.all.js ➔ createPrototypeRange 0 224 44
A bootstrap3-wysihtml5.all.js ➔ addRangeToControlSelection 0 21 3
A bootstrap3-wysihtml5.all.js ➔ rangeToHtml 0 6 1
A bootstrap3-wysihtml5.all.js ➔ saveRanges 0 26 4
A bootstrap3-wysihtml5.all.js ➔ program5 0 5 1
F bootstrap3-wysihtml5.all.js ➔ hasClass 0 8 150
A bootstrap3-wysihtml5.all.js ➔ getWindow 0 10 4
A bootstrap3-wysihtml5.all.js ➔ program3 0 5 1
A bootstrap3-wysihtml5.all.js ➔ removeCellSelections 0 10 4
F bootstrap3-wysihtml5.all.js ➔ isDirectionBackward 0 3 84
A bootstrap3-wysihtml5.all.js ➔ warn 0 3 1
A bootstrap3-wysihtml5.all.js ➔ f 0 1 2
A bootstrap3-wysihtml5.all.js ➔ _handleComment 0 5 2
F bootstrap3-wysihtml5.all.js ➔ cleanPastedHTML 0 36 221
B bootstrap3-wysihtml5.all.js ➔ getMatchingStyleRegexp 0 23 6
F bootstrap3-wysihtml5.all.js ➔ assertNodeInSameDocument 0 5 68
A bootstrap3-wysihtml5.all.js ➔ assertNoDocTypeNotationEntityAncestor 0 5 2
A bootstrap3-wysihtml5.all.js ➔ winSelectionIsBackward 0 7 2
A bootstrap3-wysihtml5.all.js ➔ getWinSelection 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _isLineBreakOrBlockElement 0 11 3
A bootstrap3-wysihtml5.all.js ➔ _hasStyle 0 7 2
A bootstrap3-wysihtml5.all.js ➔ i 0 1 1
F bootstrap3-wysihtml5.all.js ➔ _convert 0 95 19
A bootstrap3-wysihtml5.all.js ➔ setRangeBoundary 0 9 3
A bootstrap3-wysihtml5.all.js ➔ isRangeValid 0 7 1
A bootstrap3-wysihtml5.all.js ➔ updateEmptySelection 0 7 1
A bootstrap3-wysihtml5.all.js ➔ program11 0 5 1
A bootstrap3-wysihtml5.all.js ➔ Range 0 8 1
A bootstrap3-wysihtml5.all.js ➔ Merge 0 5 2
A bootstrap3-wysihtml5.all.js ➔ Module 0 7 1
F bootstrap3-wysihtml5.all.js ➔ _addClass 0 8 43
A bootstrap3-wysihtml5.all.js ➔ alertOrLog 0 7 2
A bootstrap3-wysihtml5.all.js ➔ restoreSelection 0 16 3
F bootstrap3-wysihtml5.all.js ➔ iosVersion 0 3 33
F bootstrap3-wysihtml5.all.js ➔ _getCumulativeOffsetTop 0 10 29
A bootstrap3-wysihtml5.all.js ➔ updateCollapsedAndCommonAncestor 0 5 2
A bootstrap3-wysihtml5.all.js ➔ getDocument 0 13 5
A bootstrap3-wysihtml5.all.js ➔ compareRanges 0 3 1
A bootstrap3-wysihtml5.all.js ➔ isOrphan 0 4 1
A bootstrap3-wysihtml5.all.js ➔ _removeLastChildIfLineBreak 0 6 2
A bootstrap3-wysihtml5.all.js ➔ isWindow 0 3 1
B bootstrap3-wysihtml5.all.js ➔ camelize 0 5 7
A bootstrap3-wysihtml5.all.js ➔ handleSelectionMousedown 0 14 2
A bootstrap3-wysihtml5.all.js ➔ nextNode 0 10 3
F bootstrap3-wysihtml5.all.js ➔ inspect 0 5 21
A bootstrap3-wysihtml5.all.js ➔ isValidOffset 0 3 2
F bootstrap3-wysihtml5.all.js ➔ _handleElement 0 70 14
A bootstrap3-wysihtml5.all.js ➔ handleMouseMove 0 22 5
A bootstrap3-wysihtml5.all.js ➔ insertRangeBoundaryMarker 0 20 1
A bootstrap3-wysihtml5.all.js ➔ _removeClass 0 8 2
F bootstrap3-wysihtml5.all.js ➔ createStartOrEndSetter 0 13 24
B bootstrap3-wysihtml5.all.js ➔ inspectNode 0 16 7
A bootstrap3-wysihtml5.all.js ➔ rangesIntersect 0 13 3
A bootstrap3-wysihtml5.all.js ➔ updateNativeRange 0 11 2
D bootstrap3-wysihtml5.all.js ➔ d 0 1 12
D bootstrap3-wysihtml5.all.js ➔ splitRangeBoundaries 0 22 13
B bootstrap3-wysihtml5.all.js ➔ restoreRange 0 34 6
A bootstrap3-wysihtml5.all.js ➔ removeMarkers 0 12 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like view/js/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// TODO: in future try to replace most inline compability checks with polyfills for code readability 
2
3
// element.textContent polyfill.
4
// Unsupporting browsers: IE8
5
6
if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get) {
0 ignored issues
show
Bug introduced by
The variable Element seems to be never declared. If this is a global, consider adding a /** global: Element */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
7
	(function() {
8
		var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText");
0 ignored issues
show
Bug introduced by
The variable Element seems to be never declared. If this is a global, consider adding a /** global: Element */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9
		Object.defineProperty(Element.prototype, "textContent",
10
			{
11
				get: function() {
12
					return innerText.get.call(this);
13
				},
14
				set: function(s) {
15
					return innerText.set.call(this, s);
16
				}
17
			}
18
		);
19
	})();
20
}
21
22
// isArray polyfill for ie8
23
if(!Array.isArray) {
24
  Array.isArray = function(arg) {
0 ignored issues
show
Compatibility Best Practice introduced by
You are extending the built-in type Array. This may have unintended consequences on other objects using this built-in type. Consider subclassing instead.
Loading history...
25
    return Object.prototype.toString.call(arg) === '[object Array]';
26
  };
27
};/**
28
 * @license wysihtml5x v0.4.13
29
 * https://github.com/Edicy/wysihtml5
30
 *
31
 * Author: Christopher Blum (https://github.com/tiff)
32
 * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
33
 *
34
 * Copyright (C) 2012 XING AG
35
 * Licensed under the MIT license (MIT)
36
 *
37
 */
38
var wysihtml5 = {
39
  version: "0.4.13",
40
41
  // namespaces
42
  commands:   {},
43
  dom:        {},
44
  quirks:     {},
45
  toolbar:    {},
46
  lang:       {},
47
  selection:  {},
48
  views:      {},
49
50
  INVISIBLE_SPACE: "\uFEFF",
51
52
  EMPTY_FUNCTION: function() {},
53
54
  ELEMENT_NODE: 1,
55
  TEXT_NODE:    3,
56
57
  BACKSPACE_KEY:  8,
58
  ENTER_KEY:      13,
59
  ESCAPE_KEY:     27,
60
  SPACE_KEY:      32,
61
  DELETE_KEY:     46
62
};
63
;/**
64
 * Rangy, a cross-browser JavaScript range and selection library
65
 * http://code.google.com/p/rangy/
66
 *
67
 * Copyright 2014, Tim Down
68
 * Licensed under the MIT license.
69
 * Version: 1.3alpha.20140804
70
 * Build date: 4 August 2014
71
 */
72
73
(function(factory, global) {
74
    if (typeof define == "function" && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
75
        // AMD. Register as an anonymous module.
76
        define(factory);
77
/*
78
    TODO: look into this properly.
79
    
80
    } else if (typeof exports == "object") {
81
        // Node/CommonJS style for Browserify
82
        module.exports = factory;
83
*/
84
    } else {
85
        // No AMD or CommonJS support so we place Rangy in a global variable
86
        global.rangy = factory();
87
    }
88
})(function() {
89
90
    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
91
92
    // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
93
    // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
94
    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
95
        "commonAncestorContainer"];
96
97
    // Minimal set of methods required for DOM Level 2 Range compliance
98
    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
99
        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
100
        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
101
102
    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
103
104
    // Subset of TextRange's full set of methods that we're interested in
105
    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
106
        "setEndPoint", "getBoundingClientRect"];
107
108
    /*----------------------------------------------------------------------------------------------------------------*/
109
110
    // Trio of functions taken from Peter Michaux's article:
111
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
112
    function isHostMethod(o, p) {
113
        var t = typeof o[p];
114
        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
115
    }
116
117
    function isHostObject(o, p) {
118
        return !!(typeof o[p] == OBJECT && o[p]);
119
    }
120
121
    function isHostProperty(o, p) {
122
        return typeof o[p] != UNDEFINED;
123
    }
124
125
    // Creates a convenience function to save verbose repeated calls to tests functions
126
    function createMultiplePropertyTest(testFunc) {
127
        return function(o, props) {
128
            var i = props.length;
129
            while (i--) {
130
                if (!testFunc(o, props[i])) {
131
                    return false;
132
                }
133
            }
134
            return true;
135
        };
136
    }
137
138
    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
139
    var areHostMethods = createMultiplePropertyTest(isHostMethod);
140
    var areHostObjects = createMultiplePropertyTest(isHostObject);
141
    var areHostProperties = createMultiplePropertyTest(isHostProperty);
142
143
    function isTextRange(range) {
144
        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
145
    }
146
147
    function getBody(doc) {
148
        return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
149
    }
150
151
    var modules = {};
152
153
    var api = {
154
        version: "1.3alpha.20140804",
155
        initialized: false,
156
        supported: true,
157
158
        util: {
159
            isHostMethod: isHostMethod,
160
            isHostObject: isHostObject,
161
            isHostProperty: isHostProperty,
162
            areHostMethods: areHostMethods,
163
            areHostObjects: areHostObjects,
164
            areHostProperties: areHostProperties,
165
            isTextRange: isTextRange,
166
            getBody: getBody
167
        },
168
169
        features: {},
170
171
        modules: modules,
172
        config: {
173
            alertOnFail: true,
174
            alertOnWarn: false,
175
            preferTextRange: false,
176
            autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize
0 ignored issues
show
Bug introduced by
The variable rangyAutoInitialize seems to be never declared. If this is a global, consider adding a /** global: rangyAutoInitialize */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
177
        }
178
    };
179
180
    function consoleLog(msg) {
181
        if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
182
            window.console.log(msg);
183
        }
184
    }
185
186
    function alertOrLog(msg, shouldAlert) {
187
        if (shouldAlert) {
188
            window.alert(msg);
189
        } else  {
190
            consoleLog(msg);
191
        }
192
    }
193
194
    function fail(reason) {
195
        api.initialized = true;
196
        api.supported = false;
197
        alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
198
    }
199
200
    api.fail = fail;
201
202
    function warn(msg) {
203
        alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
204
    }
205
206
    api.warn = warn;
207
208
    // Add utility extend() method
209
    if ({}.hasOwnProperty) {
210
        api.util.extend = function(obj, props, deep) {
211
            var o, p;
212
            for (var i in props) {
213
                if (props.hasOwnProperty(i)) {
214
                    o = obj[i];
215
                    p = props[i];
216
                    if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
217
                        api.util.extend(o, p, true);
218
                    }
219
                    obj[i] = p;
220
                }
221
            }
222
            // Special case for toString, which does not show up in for...in loops in IE <= 8
223
            if (props.hasOwnProperty("toString")) {
224
                obj.toString = props.toString;
225
            }
226
            return obj;
227
        };
228
    } else {
229
        fail("hasOwnProperty not supported");
230
    }
231
232
    // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
233
    (function() {
234
        var el = document.createElement("div");
235
        el.appendChild(document.createElement("span"));
236
        var slice = [].slice;
237
        var toArray;
238
        try {
239
            if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing slice.call(el.childNodes, 0).0.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
240
                toArray = function(arrayLike) {
241
                    return slice.call(arrayLike, 0);
242
                };
243
            }
244
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
245
246
        if (!toArray) {
247
            toArray = function(arrayLike) {
248
                var arr = [];
249
                for (var i = 0, len = arrayLike.length; i < len; ++i) {
250
                    arr[i] = arrayLike[i];
251
                }
252
                return arr;
253
            };
254
        }
255
256
        api.util.toArray = toArray;
257
    })();
258
259
260
    // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
261
    // normalization of event properties
262
    var addListener;
263
    if (isHostMethod(document, "addEventListener")) {
264
        addListener = function(obj, eventType, listener) {
265
            obj.addEventListener(eventType, listener, false);
266
        };
267
    } else if (isHostMethod(document, "attachEvent")) {
268
        addListener = function(obj, eventType, listener) {
269
            obj.attachEvent("on" + eventType, listener);
270
        };
271
    } else {
272
        fail("Document does not have required addEventListener or attachEvent method");
273
    }
274
275
    api.util.addListener = addListener;
0 ignored issues
show
Bug introduced by
The variable addListener seems to not be initialized for all possible execution paths.
Loading history...
276
277
    var initListeners = [];
278
279
    function getErrorDesc(ex) {
280
        return ex.message || ex.description || String(ex);
281
    }
282
283
    // Initialization
284
    function init() {
285
        if (api.initialized) {
286
            return;
287
        }
288
        var testRange;
289
        var implementsDomRange = false, implementsTextRange = false;
290
291
        // First, perform basic feature tests
292
293
        if (isHostMethod(document, "createRange")) {
294
            testRange = document.createRange();
295
            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
296
                implementsDomRange = true;
297
            }
298
        }
299
300
        var body = getBody(document);
301
        if (!body || body.nodeName.toLowerCase() != "body") {
302
            fail("No body element found");
303
            return;
304
        }
305
306
        if (body && isHostMethod(body, "createTextRange")) {
307
            testRange = body.createTextRange();
308
            if (isTextRange(testRange)) {
309
                implementsTextRange = true;
310
            }
311
        }
312
313
        if (!implementsDomRange && !implementsTextRange) {
314
            fail("Neither Range nor TextRange are available");
315
            return;
316
        }
317
318
        api.initialized = true;
319
        api.features = {
320
            implementsDomRange: implementsDomRange,
321
            implementsTextRange: implementsTextRange
322
        };
323
324
        // Initialize modules
325
        var module, errorMessage;
326
        for (var moduleName in modules) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
327
            if ( (module = modules[moduleName]) instanceof Module ) {
328
                module.init(module, api);
329
            }
330
        }
331
332
        // Call init listeners
333
        for (var i = 0, len = initListeners.length; i < len; ++i) {
334
            try {
335
                initListeners[i](api);
336
            } catch (ex) {
337
                errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
338
                consoleLog(errorMessage);
339
            }
340
        }
341
    }
342
343
    // Allow external scripts to initialize this library in case it's loaded after the document has loaded
344
    api.init = init;
345
346
    // Execute listener immediately if already initialized
347
    api.addInitListener = function(listener) {
348
        if (api.initialized) {
349
            listener(api);
350
        } else {
351
            initListeners.push(listener);
352
        }
353
    };
354
355
    var shimListeners = [];
356
357
    api.addShimListener = function(listener) {
358
        shimListeners.push(listener);
359
    };
360
361
    function shim(win) {
362
        win = win || window;
363
        init();
364
365
        // Notify listeners
366
        for (var i = 0, len = shimListeners.length; i < len; ++i) {
367
            shimListeners[i](win);
368
        }
369
    }
370
371
    api.shim = api.createMissingNativeApi = shim;
372
373
    function Module(name, dependencies, initializer) {
374
        this.name = name;
375
        this.dependencies = dependencies;
376
        this.initialized = false;
377
        this.supported = false;
378
        this.initializer = initializer;
379
    }
380
381
    Module.prototype = {
382
        init: function() {
383
            var requiredModuleNames = this.dependencies || [];
384
            for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
385
                moduleName = requiredModuleNames[i];
386
387
                requiredModule = modules[moduleName];
388
                if (!requiredModule || !(requiredModule instanceof Module)) {
389
                    throw new Error("required module '" + moduleName + "' not found");
390
                }
391
392
                requiredModule.init();
393
394
                if (!requiredModule.supported) {
395
                    throw new Error("required module '" + moduleName + "' not supported");
396
                }
397
            }
398
            
399
            // Now run initializer
400
            this.initializer(this);
401
        },
402
        
403
        fail: function(reason) {
404
            this.initialized = true;
405
            this.supported = false;
406
            throw new Error("Module '" + this.name + "' failed to load: " + reason);
407
        },
408
409
        warn: function(msg) {
410
            api.warn("Module " + this.name + ": " + msg);
411
        },
412
413
        deprecationNotice: function(deprecated, replacement) {
414
            api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " +
415
                replacement + " instead");
416
        },
417
418
        createError: function(msg) {
419
            return new Error("Error in Rangy " + this.name + " module: " + msg);
420
        }
421
    };
422
    
423
    function createModule(isCore, name, dependencies, initFunc) {
424
        var newModule = new Module(name, dependencies, function(module) {
425
            if (!module.initialized) {
426
                module.initialized = true;
427
                try {
428
                    initFunc(api, module);
429
                    module.supported = true;
430
                } catch (ex) {
431
                    var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
432
                    consoleLog(errorMessage);
433
                }
434
            }
435
        });
436
        modules[name] = newModule;
437
    }
438
439
    api.createModule = function(name) {
440
        // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
441
        var initFunc, dependencies;
442
        if (arguments.length == 2) {
443
            initFunc = arguments[1];
444
            dependencies = [];
445
        } else {
446
            initFunc = arguments[2];
447
            dependencies = arguments[1];
448
        }
449
450
        var module = createModule(false, name, dependencies, initFunc);
451
452
        // Initialize the module immediately if the core is already initialized
453
        if (api.initialized) {
454
            module.init();
455
        }
456
    };
457
458
    api.createCoreModule = function(name, dependencies, initFunc) {
459
        createModule(true, name, dependencies, initFunc);
460
    };
461
462
    /*----------------------------------------------------------------------------------------------------------------*/
463
464
    // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
465
466
    function RangePrototype() {}
467
    api.RangePrototype = RangePrototype;
468
    api.rangePrototype = new RangePrototype();
469
470
    function SelectionPrototype() {}
471
    api.selectionPrototype = new SelectionPrototype();
472
473
    /*----------------------------------------------------------------------------------------------------------------*/
474
475
    // Wait for document to load before running tests
476
477
    var docReady = false;
478
479
    var loadHandler = function(e) {
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
480
        if (!docReady) {
481
            docReady = true;
482
            if (!api.initialized && api.config.autoInitialize) {
483
                init();
484
            }
485
        }
486
    };
487
488
    // Test whether we have window and document objects that we will need
489
    if (typeof window == UNDEFINED) {
490
        fail("No window found");
491
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
492
    }
493
    if (typeof document == UNDEFINED) {
494
        fail("No document found");
495
        return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
496
    }
497
498
    if (isHostMethod(document, "addEventListener")) {
499
        document.addEventListener("DOMContentLoaded", loadHandler, false);
500
    }
501
502
    // Add a fallback in case the DOMContentLoaded event isn't supported
503
    addListener(window, "load", loadHandler);
504
505
    /*----------------------------------------------------------------------------------------------------------------*/
506
    
507
    // DOM utility methods used by Rangy
508
    api.createCoreModule("DomUtil", [], function(api, module) {
509
        var UNDEF = "undefined";
510
        var util = api.util;
511
512
        // Perform feature tests
513
        if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
514
            module.fail("document missing a Node creation method");
515
        }
516
517
        if (!util.isHostMethod(document, "getElementsByTagName")) {
518
            module.fail("document missing getElementsByTagName method");
519
        }
520
521
        var el = document.createElement("div");
522
        if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
523
                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
524
            module.fail("Incomplete Element implementation");
525
        }
526
527
        // innerHTML is required for Range's createContextualFragment method
528
        if (!util.isHostProperty(el, "innerHTML")) {
529
            module.fail("Element is missing innerHTML property");
530
        }
531
532
        var textNode = document.createTextNode("test");
533
        if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
534
                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
535
                !util.areHostProperties(textNode, ["data"]))) {
536
            module.fail("Incomplete Text Node implementation");
537
        }
538
539
        /*----------------------------------------------------------------------------------------------------------------*/
540
541
        // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
542
        // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
543
        // contains just the document as a single element and the value searched for is the document.
544
        var arrayContains = /*Array.prototype.indexOf ?
545
            function(arr, val) {
546
                return arr.indexOf(val) > -1;
547
            }:*/
548
549
            function(arr, val) {
550
                var i = arr.length;
551
                while (i--) {
552
                    if (arr[i] === val) {
553
                        return true;
554
                    }
555
                }
556
                return false;
557
            };
558
559
        // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
560
        function isHtmlNamespace(node) {
561
            var ns;
562
            return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
563
        }
564
565
        function parentElement(node) {
566
            var parent = node.parentNode;
567
            return (parent.nodeType == 1) ? parent : null;
0 ignored issues
show
Best Practice introduced by
Comparing parent.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
568
        }
569
570
        function getNodeIndex(node) {
571
            var i = 0;
572
            while( (node = node.previousSibling) ) {
573
                ++i;
574
            }
575
            return i;
576
        }
577
578
        function getNodeLength(node) {
579
            switch (node.nodeType) {
580
                case 7:
581
                case 10:
582
                    return 0;
583
                case 3:
584
                case 8:
585
                    return node.length;
586
                default:
587
                    return node.childNodes.length;
588
            }
589
        }
590
591
        function getCommonAncestor(node1, node2) {
592
            var ancestors = [], n;
593
            for (n = node1; n; n = n.parentNode) {
594
                ancestors.push(n);
595
            }
596
597
            for (n = node2; n; n = n.parentNode) {
598
                if (arrayContains(ancestors, n)) {
599
                    return n;
600
                }
601
            }
602
603
            return null;
604
        }
605
606
        function isAncestorOf(ancestor, descendant, selfIsAncestor) {
607
            var n = selfIsAncestor ? descendant : descendant.parentNode;
608
            while (n) {
609
                if (n === ancestor) {
610
                    return true;
611
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
612
                    n = n.parentNode;
613
                }
614
            }
615
            return false;
616
        }
617
618
        function isOrIsAncestorOf(ancestor, descendant) {
619
            return isAncestorOf(ancestor, descendant, true);
620
        }
621
622
        function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
623
            var p, n = selfIsAncestor ? node : node.parentNode;
624
            while (n) {
625
                p = n.parentNode;
626
                if (p === ancestor) {
627
                    return n;
628
                }
629
                n = p;
630
            }
631
            return null;
632
        }
633
634
        function isCharacterDataNode(node) {
635
            var t = node.nodeType;
636
            return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
637
        }
638
639
        function isTextOrCommentNode(node) {
640
            if (!node) {
641
                return false;
642
            }
643
            var t = node.nodeType;
644
            return t == 3 || t == 8 ; // Text or Comment
645
        }
646
647
        function insertAfter(node, precedingNode) {
648
            var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
649
            if (nextNode) {
650
                parent.insertBefore(node, nextNode);
651
            } else {
652
                parent.appendChild(node);
653
            }
654
            return node;
655
        }
656
657
        // Note that we cannot use splitText() because it is bugridden in IE 9.
658
        function splitDataNode(node, index, positionsToPreserve) {
659
            var newNode = node.cloneNode(false);
660
            newNode.deleteData(0, index);
661
            node.deleteData(index, node.length - index);
662
            insertAfter(newNode, node);
663
664
            // Preserve positions
665
            if (positionsToPreserve) {
666
                for (var i = 0, position; position = positionsToPreserve[i++]; ) {
667
                    // Handle case where position was inside the portion of node after the split point
668
                    if (position.node == node && position.offset > index) {
669
                        position.node = newNode;
670
                        position.offset -= index;
671
                    }
672
                    // Handle the case where the position is a node offset within node's parent
673
                    else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
674
                        ++position.offset;
675
                    }
676
                }
677
            }
678
            return newNode;
679
        }
680
681
        function getDocument(node) {
682
            if (node.nodeType == 9) {
683
                return node;
684
            } else if (typeof node.ownerDocument != UNDEF) {
685
                return node.ownerDocument;
686
            } else if (typeof node.document != UNDEF) {
687
                return node.document;
688
            } else if (node.parentNode) {
689
                return getDocument(node.parentNode);
690
            } else {
691
                throw module.createError("getDocument: no document found for node");
692
            }
693
        }
694
695
        function getWindow(node) {
696
            var doc = getDocument(node);
697
            if (typeof doc.defaultView != UNDEF) {
698
                return doc.defaultView;
699
            } else if (typeof doc.parentWindow != UNDEF) {
700
                return doc.parentWindow;
701
            } else {
702
                throw module.createError("Cannot get a window object for node");
703
            }
704
        }
705
706
        function getIframeDocument(iframeEl) {
707
            if (typeof iframeEl.contentDocument != UNDEF) {
708
                return iframeEl.contentDocument;
709
            } else if (typeof iframeEl.contentWindow != UNDEF) {
710
                return iframeEl.contentWindow.document;
711
            } else {
712
                throw module.createError("getIframeDocument: No Document object found for iframe element");
713
            }
714
        }
715
716
        function getIframeWindow(iframeEl) {
717
            if (typeof iframeEl.contentWindow != UNDEF) {
718
                return iframeEl.contentWindow;
719
            } else if (typeof iframeEl.contentDocument != UNDEF) {
720
                return iframeEl.contentDocument.defaultView;
721
            } else {
722
                throw module.createError("getIframeWindow: No Window object found for iframe element");
723
            }
724
        }
725
726
        // This looks bad. Is it worth it?
727
        function isWindow(obj) {
728
            return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
729
        }
730
731
        function getContentDocument(obj, module, methodName) {
732
            var doc;
733
734
            if (!obj) {
735
                doc = document;
736
            }
737
738
            // Test if a DOM node has been passed and obtain a document object for it if so
739
            else if (util.isHostProperty(obj, "nodeType")) {
740
                doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ?
0 ignored issues
show
Best Practice introduced by
Comparing obj.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
741
                    getIframeDocument(obj) : getDocument(obj);
742
            }
743
744
            // Test if the doc parameter appears to be a Window object
745
            else if (isWindow(obj)) {
746
                doc = obj.document;
747
            }
748
749
            if (!doc) {
750
                throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
751
            }
752
753
            return doc;
754
        }
755
756
        function getRootContainer(node) {
757
            var parent;
758
            while ( (parent = node.parentNode) ) {
759
                node = parent;
760
            }
761
            return node;
762
        }
763
764
        function comparePoints(nodeA, offsetA, nodeB, offsetB) {
765
            // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
766
            var nodeC, root, childA, childB, n;
767
            if (nodeA == nodeB) {
768
                // Case 1: nodes are the same
769
                return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
770
            } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
771
                // Case 2: node C (container B or an ancestor) is a child node of A
772
                return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
773
            } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
774
                // Case 3: node C (container A or an ancestor) is a child node of B
775
                return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
776
            } else {
777
                root = getCommonAncestor(nodeA, nodeB);
778
                if (!root) {
779
                    throw new Error("comparePoints error: nodes have no common ancestor");
780
                }
781
782
                // Case 4: containers are siblings or descendants of siblings
783
                childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
784
                childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
785
786
                if (childA === childB) {
787
                    // This shouldn't be possible
788
                    throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
789
                } else {
790
                    n = root.firstChild;
791
                    while (n) {
792
                        if (n === childA) {
793
                            return -1;
794
                        } else if (n === childB) {
795
                            return 1;
796
                        }
797
                        n = n.nextSibling;
798
                    }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
799
                }
800
            }
801
        }
802
803
        /*----------------------------------------------------------------------------------------------------------------*/
804
805
        // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
806
        var crashyTextNodes = false;
807
808
        function isBrokenNode(node) {
809
            var n;
810
            try {
811
                n = node.parentNode;
0 ignored issues
show
Unused Code introduced by
The variable n seems to be never used. Consider removing it.
Loading history...
812
                return false;
813
            } catch (e) {
814
                return true;
815
            }
816
        }
817
818
        (function() {
819
            var el = document.createElement("b");
820
            el.innerHTML = "1";
821
            var textNode = el.firstChild;
822
            el.innerHTML = "<br>";
823
            crashyTextNodes = isBrokenNode(textNode);
824
825
            api.features.crashyTextNodes = crashyTextNodes;
826
        })();
827
828
        /*----------------------------------------------------------------------------------------------------------------*/
829
830
        function inspectNode(node) {
831
            if (!node) {
832
                return "[No node]";
833
            }
834
            if (crashyTextNodes && isBrokenNode(node)) {
835
                return "[Broken node]";
836
            }
837
            if (isCharacterDataNode(node)) {
838
                return '"' + node.data + '"';
839
            }
840
            if (node.nodeType == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing node.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
841
                var idAttr = node.id ? ' id="' + node.id + '"' : "";
842
                return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
843
            }
844
            return node.nodeName;
845
        }
846
847
        function fragmentFromNodeChildren(node) {
848
            var fragment = getDocument(node).createDocumentFragment(), child;
849
            while ( (child = node.firstChild) ) {
850
                fragment.appendChild(child);
851
            }
852
            return fragment;
853
        }
854
855
        var getComputedStyleProperty;
856
        if (typeof window.getComputedStyle != UNDEF) {
857
            getComputedStyleProperty = function(el, propName) {
858
                return getWindow(el).getComputedStyle(el, null)[propName];
859
            };
860
        } else if (typeof document.documentElement.currentStyle != UNDEF) {
861
            getComputedStyleProperty = function(el, propName) {
862
                return el.currentStyle[propName];
863
            };
864
        } else {
865
            module.fail("No means of obtaining computed style properties found");
866
        }
867
868
        function NodeIterator(root) {
869
            this.root = root;
870
            this._next = root;
871
        }
872
873
        NodeIterator.prototype = {
874
            _current: null,
875
876
            hasNext: function() {
877
                return !!this._next;
878
            },
879
880
            next: function() {
881
                var n = this._current = this._next;
882
                var child, next;
883
                if (this._current) {
884
                    child = n.firstChild;
885
                    if (child) {
886
                        this._next = child;
887
                    } else {
888
                        next = null;
889
                        while ((n !== this.root) && !(next = n.nextSibling)) {
890
                            n = n.parentNode;
891
                        }
892
                        this._next = next;
893
                    }
894
                }
895
                return this._current;
896
            },
897
898
            detach: function() {
899
                this._current = this._next = this.root = null;
900
            }
901
        };
902
903
        function createIterator(root) {
904
            return new NodeIterator(root);
905
        }
906
907
        function DomPosition(node, offset) {
908
            this.node = node;
909
            this.offset = offset;
910
        }
911
912
        DomPosition.prototype = {
913
            equals: function(pos) {
914
                return !!pos && this.node === pos.node && this.offset == pos.offset;
915
            },
916
917
            inspect: function() {
918
                return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
919
            },
920
921
            toString: function() {
922
                return this.inspect();
923
            }
924
        };
925
926
        function DOMException(codeName) {
927
            this.code = this[codeName];
928
            this.codeName = codeName;
929
            this.message = "DOMException: " + this.codeName;
930
        }
931
932
        DOMException.prototype = {
933
            INDEX_SIZE_ERR: 1,
934
            HIERARCHY_REQUEST_ERR: 3,
935
            WRONG_DOCUMENT_ERR: 4,
936
            NO_MODIFICATION_ALLOWED_ERR: 7,
937
            NOT_FOUND_ERR: 8,
938
            NOT_SUPPORTED_ERR: 9,
939
            INVALID_STATE_ERR: 11,
940
            INVALID_NODE_TYPE_ERR: 24
941
        };
942
943
        DOMException.prototype.toString = function() {
944
            return this.message;
945
        };
946
947
        api.dom = {
948
            arrayContains: arrayContains,
949
            isHtmlNamespace: isHtmlNamespace,
950
            parentElement: parentElement,
951
            getNodeIndex: getNodeIndex,
952
            getNodeLength: getNodeLength,
953
            getCommonAncestor: getCommonAncestor,
954
            isAncestorOf: isAncestorOf,
955
            isOrIsAncestorOf: isOrIsAncestorOf,
956
            getClosestAncestorIn: getClosestAncestorIn,
957
            isCharacterDataNode: isCharacterDataNode,
958
            isTextOrCommentNode: isTextOrCommentNode,
959
            insertAfter: insertAfter,
960
            splitDataNode: splitDataNode,
961
            getDocument: getDocument,
962
            getWindow: getWindow,
963
            getIframeWindow: getIframeWindow,
964
            getIframeDocument: getIframeDocument,
965
            getBody: util.getBody,
966
            isWindow: isWindow,
967
            getContentDocument: getContentDocument,
968
            getRootContainer: getRootContainer,
969
            comparePoints: comparePoints,
970
            isBrokenNode: isBrokenNode,
971
            inspectNode: inspectNode,
972
            getComputedStyleProperty: getComputedStyleProperty,
0 ignored issues
show
Bug introduced by
The variable getComputedStyleProperty seems to not be initialized for all possible execution paths.
Loading history...
973
            fragmentFromNodeChildren: fragmentFromNodeChildren,
974
            createIterator: createIterator,
975
            DomPosition: DomPosition
976
        };
977
978
        api.DOMException = DOMException;
979
    });
980
981
    /*----------------------------------------------------------------------------------------------------------------*/
982
983
    // Pure JavaScript implementation of DOM Range
984
    api.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
0 ignored issues
show
Unused Code introduced by
The parameter module is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
985
        var dom = api.dom;
986
        var util = api.util;
987
        var DomPosition = dom.DomPosition;
988
        var DOMException = api.DOMException;
989
990
        var isCharacterDataNode = dom.isCharacterDataNode;
991
        var getNodeIndex = dom.getNodeIndex;
992
        var isOrIsAncestorOf = dom.isOrIsAncestorOf;
993
        var getDocument = dom.getDocument;
994
        var comparePoints = dom.comparePoints;
995
        var splitDataNode = dom.splitDataNode;
996
        var getClosestAncestorIn = dom.getClosestAncestorIn;
997
        var getNodeLength = dom.getNodeLength;
998
        var arrayContains = dom.arrayContains;
999
        var getRootContainer = dom.getRootContainer;
1000
        var crashyTextNodes = api.features.crashyTextNodes;
1001
1002
        /*----------------------------------------------------------------------------------------------------------------*/
1003
1004
        // Utility functions
1005
1006
        function isNonTextPartiallySelected(node, range) {
1007
            return (node.nodeType != 3) &&
1008
                   (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
1009
        }
1010
1011
        function getRangeDocument(range) {
1012
            return range.document || getDocument(range.startContainer);
1013
        }
1014
1015
        function getBoundaryBeforeNode(node) {
1016
            return new DomPosition(node.parentNode, getNodeIndex(node));
1017
        }
1018
1019
        function getBoundaryAfterNode(node) {
1020
            return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
1021
        }
1022
1023
        function insertNodeAtPosition(node, n, o) {
1024
            var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
1025
            if (isCharacterDataNode(n)) {
1026
                if (o == n.length) {
1027
                    dom.insertAfter(node, n);
1028
                } else {
1029
                    n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
0 ignored issues
show
Best Practice introduced by
Comparing o to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1030
                }
1031
            } else if (o >= n.childNodes.length) {
1032
                n.appendChild(node);
1033
            } else {
1034
                n.insertBefore(node, n.childNodes[o]);
1035
            }
1036
            return firstNodeInserted;
1037
        }
1038
1039
        function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
1040
            assertRangeValid(rangeA);
1041
            assertRangeValid(rangeB);
1042
1043
            if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
1044
                throw new DOMException("WRONG_DOCUMENT_ERR");
1045
            }
1046
1047
            var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
1048
                endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
1049
1050
            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1051
        }
1052
1053
        function cloneSubtree(iterator) {
1054
            var partiallySelected;
1055
            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1056
                partiallySelected = iterator.isPartiallySelectedSubtree();
1057
                node = node.cloneNode(!partiallySelected);
1058
                if (partiallySelected) {
1059
                    subIterator = iterator.getSubtreeIterator();
1060
                    node.appendChild(cloneSubtree(subIterator));
1061
                    subIterator.detach();
1062
                }
1063
1064
                if (node.nodeType == 10) { // DocumentType
1065
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1066
                }
1067
                frag.appendChild(node);
1068
            }
1069
            return frag;
1070
        }
1071
1072
        function iterateSubtree(rangeIterator, func, iteratorState) {
1073
            var it, n;
1074
            iteratorState = iteratorState || { stop: false };
1075
            for (var node, subRangeIterator; node = rangeIterator.next(); ) {
1076
                if (rangeIterator.isPartiallySelectedSubtree()) {
1077
                    if (func(node) === false) {
1078
                        iteratorState.stop = true;
1079
                        return;
1080
                    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1081
                        // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
1082
                        // the node selected by the Range.
1083
                        subRangeIterator = rangeIterator.getSubtreeIterator();
1084
                        iterateSubtree(subRangeIterator, func, iteratorState);
1085
                        subRangeIterator.detach();
1086
                        if (iteratorState.stop) {
1087
                            return;
1088
                        }
1089
                    }
1090
                } else {
1091
                    // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
1092
                    // descendants
1093
                    it = dom.createIterator(node);
1094
                    while ( (n = it.next()) ) {
1095
                        if (func(n) === false) {
1096
                            iteratorState.stop = true;
1097
                            return;
1098
                        }
1099
                    }
1100
                }
1101
            }
1102
        }
1103
1104
        function deleteSubtree(iterator) {
1105
            var subIterator;
1106
            while (iterator.next()) {
1107
                if (iterator.isPartiallySelectedSubtree()) {
1108
                    subIterator = iterator.getSubtreeIterator();
1109
                    deleteSubtree(subIterator);
1110
                    subIterator.detach();
1111
                } else {
1112
                    iterator.remove();
1113
                }
1114
            }
1115
        }
1116
1117
        function extractSubtree(iterator) {
1118
            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1119
1120
                if (iterator.isPartiallySelectedSubtree()) {
1121
                    node = node.cloneNode(false);
1122
                    subIterator = iterator.getSubtreeIterator();
1123
                    node.appendChild(extractSubtree(subIterator));
1124
                    subIterator.detach();
1125
                } else {
1126
                    iterator.remove();
1127
                }
1128
                if (node.nodeType == 10) { // DocumentType
1129
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1130
                }
1131
                frag.appendChild(node);
1132
            }
1133
            return frag;
1134
        }
1135
1136
        function getNodesInRange(range, nodeTypes, filter) {
1137
            var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
1138
            var filterExists = !!filter;
1139
            if (filterNodeTypes) {
1140
                regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
1141
            }
1142
1143
            var nodes = [];
1144
            iterateSubtree(new RangeIterator(range, false), function(node) {
1145
                if (filterNodeTypes && !regex.test(node.nodeType)) {
0 ignored issues
show
Bug introduced by
The variable regex does not seem to be initialized in case filterNodeTypes on line 1139 is false. Are you sure this can never be the case?
Loading history...
1146
                    return;
1147
                }
1148
                if (filterExists && !filter(node)) {
1149
                    return;
1150
                }
1151
                // Don't include a boundary container if it is a character data node and the range does not contain any
1152
                // of its character data. See issue 190.
1153
                var sc = range.startContainer;
1154
                if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
1155
                    return;
1156
                }
1157
1158
                var ec = range.endContainer;
1159
                if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing range.endOffset to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1160
                    return;
1161
                }
1162
1163
                nodes.push(node);
1164
            });
1165
            return nodes;
1166
        }
1167
1168
        function inspect(range) {
1169
            var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
1170
            return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
1171
                    dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
1172
        }
1173
1174
        /*----------------------------------------------------------------------------------------------------------------*/
1175
1176
        // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
1177
1178
        function RangeIterator(range, clonePartiallySelectedTextNodes) {
1179
            this.range = range;
1180
            this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
1181
1182
1183
            if (!range.collapsed) {
1184
                this.sc = range.startContainer;
1185
                this.so = range.startOffset;
1186
                this.ec = range.endContainer;
1187
                this.eo = range.endOffset;
1188
                var root = range.commonAncestorContainer;
1189
1190
                if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
1191
                    this.isSingleCharacterDataNode = true;
1192
                    this._first = this._last = this._next = this.sc;
1193
                } else {
1194
                    this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
1195
                        this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
1196
                    this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
1197
                        this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
1198
                }
1199
            }
1200
        }
1201
1202
        RangeIterator.prototype = {
1203
            _current: null,
1204
            _next: null,
1205
            _first: null,
1206
            _last: null,
1207
            isSingleCharacterDataNode: false,
1208
1209
            reset: function() {
1210
                this._current = null;
1211
                this._next = this._first;
1212
            },
1213
1214
            hasNext: function() {
1215
                return !!this._next;
1216
            },
1217
1218
            next: function() {
1219
                // Move to next node
1220
                var current = this._current = this._next;
1221
                if (current) {
1222
                    this._next = (current !== this._last) ? current.nextSibling : null;
1223
1224
                    // Check for partially selected text nodes
1225
                    if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
1226
                        if (current === this.ec) {
1227
                            (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
1228
                        }
1229
                        if (this._current === this.sc) {
1230
                            (current = current.cloneNode(true)).deleteData(0, this.so);
1231
                        }
1232
                    }
1233
                }
1234
1235
                return current;
1236
            },
1237
1238
            remove: function() {
1239
                var current = this._current, start, end;
1240
1241
                if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
1242
                    start = (current === this.sc) ? this.so : 0;
1243
                    end = (current === this.ec) ? this.eo : current.length;
1244
                    if (start != end) {
1245
                        current.deleteData(start, end - start);
1246
                    }
1247
                } else {
1248
                    if (current.parentNode) {
1249
                        current.parentNode.removeChild(current);
1250
                    } else {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
1251
                    }
1252
                }
1253
            },
1254
1255
            // Checks if the current node is partially selected
1256
            isPartiallySelectedSubtree: function() {
1257
                var current = this._current;
1258
                return isNonTextPartiallySelected(current, this.range);
1259
            },
1260
1261
            getSubtreeIterator: function() {
1262
                var subRange;
1263
                if (this.isSingleCharacterDataNode) {
1264
                    subRange = this.range.cloneRange();
1265
                    subRange.collapse(false);
1266
                } else {
1267
                    subRange = new Range(getRangeDocument(this.range));
1268
                    var current = this._current;
1269
                    var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
1270
1271
                    if (isOrIsAncestorOf(current, this.sc)) {
1272
                        startContainer = this.sc;
1273
                        startOffset = this.so;
1274
                    }
1275
                    if (isOrIsAncestorOf(current, this.ec)) {
1276
                        endContainer = this.ec;
1277
                        endOffset = this.eo;
1278
                    }
1279
1280
                    updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
1281
                }
1282
                return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
1283
            },
1284
1285
            detach: function() {
1286
                this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
1287
            }
1288
        };
1289
1290
        /*----------------------------------------------------------------------------------------------------------------*/
1291
1292
        var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
1293
        var rootContainerNodeTypes = [2, 9, 11];
1294
        var readonlyNodeTypes = [5, 6, 10, 12];
1295
        var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
1296
        var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
1297
1298
        function createAncestorFinder(nodeTypes) {
1299
            return function(node, selfIsAncestor) {
1300
                var t, n = selfIsAncestor ? node : node.parentNode;
1301
                while (n) {
1302
                    t = n.nodeType;
1303
                    if (arrayContains(nodeTypes, t)) {
1304
                        return n;
1305
                    }
1306
                    n = n.parentNode;
1307
                }
1308
                return null;
1309
            };
1310
        }
1311
1312
        var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
1313
        var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
1314
        var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
1315
1316
        function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
1317
            if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
1318
                throw new DOMException("INVALID_NODE_TYPE_ERR");
1319
            }
1320
        }
1321
1322
        function assertValidNodeType(node, invalidTypes) {
1323
            if (!arrayContains(invalidTypes, node.nodeType)) {
1324
                throw new DOMException("INVALID_NODE_TYPE_ERR");
1325
            }
1326
        }
1327
1328
        function assertValidOffset(node, offset) {
1329
            if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
1330
                throw new DOMException("INDEX_SIZE_ERR");
1331
            }
1332
        }
1333
1334
        function assertSameDocumentOrFragment(node1, node2) {
1335
            if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
1336
                throw new DOMException("WRONG_DOCUMENT_ERR");
1337
            }
1338
        }
1339
1340
        function assertNodeNotReadOnly(node) {
1341
            if (getReadonlyAncestor(node, true)) {
1342
                throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
1343
            }
1344
        }
1345
1346
        function assertNode(node, codeName) {
1347
            if (!node) {
1348
                throw new DOMException(codeName);
1349
            }
1350
        }
1351
1352
        function isOrphan(node) {
1353
            return (crashyTextNodes && dom.isBrokenNode(node)) ||
1354
                !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
1355
        }
1356
1357
        function isValidOffset(node, offset) {
1358
            return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
1359
        }
1360
1361
        function isRangeValid(range) {
1362
            return (!!range.startContainer && !!range.endContainer &&
1363
                    !isOrphan(range.startContainer) &&
1364
                    !isOrphan(range.endContainer) &&
1365
                    isValidOffset(range.startContainer, range.startOffset) &&
1366
                    isValidOffset(range.endContainer, range.endOffset));
1367
        }
1368
1369
        function assertRangeValid(range) {
1370
            if (!isRangeValid(range)) {
1371
                throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
1372
            }
1373
        }
1374
1375
        /*----------------------------------------------------------------------------------------------------------------*/
1376
1377
        // Test the browser's innerHTML support to decide how to implement createContextualFragment
1378
        var styleEl = document.createElement("style");
1379
        var htmlParsingConforms = false;
1380
        try {
1381
            styleEl.innerHTML = "<b>x</b>";
1382
            htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
1383
        } catch (e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
1384
            // IE 6 and 7 throw
1385
        }
1386
1387
        api.features.htmlParsingConforms = htmlParsingConforms;
1388
1389
        var createContextualFragment = htmlParsingConforms ?
1390
1391
            // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
1392
            // discussion and base code for this implementation at issue 67.
1393
            // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
1394
            // Thanks to Aleks Williams.
1395
            function(fragmentStr) {
1396
                // "Let node the context object's start's node."
1397
                var node = this.startContainer;
1398
                var doc = getDocument(node);
1399
1400
                // "If the context object's start's node is null, raise an INVALID_STATE_ERR
1401
                // exception and abort these steps."
1402
                if (!node) {
1403
                    throw new DOMException("INVALID_STATE_ERR");
1404
                }
1405
1406
                // "Let element be as follows, depending on node's interface:"
1407
                // Document, Document Fragment: null
1408
                var el = null;
1409
1410
                // "Element: node"
1411
                if (node.nodeType == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing node.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
1412
                    el = node;
1413
1414
                // "Text, Comment: node's parentElement"
1415
                } else if (isCharacterDataNode(node)) {
1416
                    el = dom.parentElement(node);
1417
                }
1418
1419
                // "If either element is null or element's ownerDocument is an HTML document
1420
                // and element's local name is "html" and element's namespace is the HTML
1421
                // namespace"
1422
                if (el === null || (
1423
                    el.nodeName == "HTML" &&
1424
                    dom.isHtmlNamespace(getDocument(el).documentElement) &&
1425
                    dom.isHtmlNamespace(el)
1426
                )) {
1427
1428
                // "let element be a new Element with "body" as its local name and the HTML
1429
                // namespace as its namespace.""
1430
                    el = doc.createElement("body");
1431
                } else {
1432
                    el = el.cloneNode(false);
1433
                }
1434
1435
                // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
1436
                // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
1437
                // "In either case, the algorithm must be invoked with fragment as the input
1438
                // and element as the context element."
1439
                el.innerHTML = fragmentStr;
1440
1441
                // "If this raises an exception, then abort these steps. Otherwise, let new
1442
                // children be the nodes returned."
1443
1444
                // "Let fragment be a new DocumentFragment."
1445
                // "Append all new children to fragment."
1446
                // "Return fragment."
1447
                return dom.fragmentFromNodeChildren(el);
1448
            } :
1449
1450
            // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
1451
            // previous versions of Rangy used (with the exception of using a body element rather than a div)
1452
            function(fragmentStr) {
1453
                var doc = getRangeDocument(this);
1454
                var el = doc.createElement("body");
1455
                el.innerHTML = fragmentStr;
1456
1457
                return dom.fragmentFromNodeChildren(el);
1458
            };
1459
1460
        function splitRangeBoundaries(range, positionsToPreserve) {
1461
            assertRangeValid(range);
1462
1463
            var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
1464
            var startEndSame = (sc === ec);
1465
1466
            if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
1467
                splitDataNode(ec, eo, positionsToPreserve);
1468
            }
1469
1470
            if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
1471
                sc = splitDataNode(sc, so, positionsToPreserve);
1472
                if (startEndSame) {
1473
                    eo -= so;
1474
                    ec = sc;
1475
                } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
1476
                    eo++;
1477
                }
1478
                so = 0;
1479
            }
1480
            range.setStartAndEnd(sc, so, ec, eo);
1481
        }
1482
        
1483
        function rangeToHtml(range) {
1484
            assertRangeValid(range);
1485
            var container = range.commonAncestorContainer.parentNode.cloneNode(false);
1486
            container.appendChild( range.cloneContents() );
1487
            return container.innerHTML;
1488
        }
1489
1490
        /*----------------------------------------------------------------------------------------------------------------*/
1491
1492
        var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
1493
            "commonAncestorContainer"];
1494
1495
        var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
1496
        var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
1497
1498
        util.extend(api.rangePrototype, {
1499
            compareBoundaryPoints: function(how, range) {
1500
                assertRangeValid(this);
1501
                assertSameDocumentOrFragment(this.startContainer, range.startContainer);
1502
1503
                var nodeA, offsetA, nodeB, offsetB;
1504
                var prefixA = (how == e2s || how == s2s) ? "start" : "end";
1505
                var prefixB = (how == s2e || how == s2s) ? "start" : "end";
1506
                nodeA = this[prefixA + "Container"];
1507
                offsetA = this[prefixA + "Offset"];
1508
                nodeB = range[prefixB + "Container"];
1509
                offsetB = range[prefixB + "Offset"];
1510
                return comparePoints(nodeA, offsetA, nodeB, offsetB);
1511
            },
1512
1513
            insertNode: function(node) {
1514
                assertRangeValid(this);
1515
                assertValidNodeType(node, insertableNodeTypes);
1516
                assertNodeNotReadOnly(this.startContainer);
1517
1518
                if (isOrIsAncestorOf(node, this.startContainer)) {
1519
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1520
                }
1521
1522
                // No check for whether the container of the start of the Range is of a type that does not allow
1523
                // children of the type of node: the browser's DOM implementation should do this for us when we attempt
1524
                // to add the node
1525
1526
                var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
1527
                this.setStartBefore(firstNodeInserted);
1528
            },
1529
1530
            cloneContents: function() {
1531
                assertRangeValid(this);
1532
1533
                var clone, frag;
1534
                if (this.collapsed) {
1535
                    return getRangeDocument(this).createDocumentFragment();
1536
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1537
                    if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
1538
                        clone = this.startContainer.cloneNode(true);
1539
                        clone.data = clone.data.slice(this.startOffset, this.endOffset);
1540
                        frag = getRangeDocument(this).createDocumentFragment();
1541
                        frag.appendChild(clone);
1542
                        return frag;
1543
                    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1544
                        var iterator = new RangeIterator(this, true);
1545
                        clone = cloneSubtree(iterator);
1546
                        iterator.detach();
1547
                    }
1548
                    return clone;
1549
                }
1550
            },
1551
1552
            canSurroundContents: function() {
1553
                assertRangeValid(this);
1554
                assertNodeNotReadOnly(this.startContainer);
1555
                assertNodeNotReadOnly(this.endContainer);
1556
1557
                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1558
                // no non-text nodes.
1559
                var iterator = new RangeIterator(this, true);
1560
                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1561
                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1562
                iterator.detach();
1563
                return !boundariesInvalid;
1564
            },
1565
1566
            surroundContents: function(node) {
1567
                assertValidNodeType(node, surroundNodeTypes);
1568
1569
                if (!this.canSurroundContents()) {
1570
                    throw new DOMException("INVALID_STATE_ERR");
1571
                }
1572
1573
                // Extract the contents
1574
                var content = this.extractContents();
1575
1576
                // Clear the children of the node
1577
                if (node.hasChildNodes()) {
1578
                    while (node.lastChild) {
1579
                        node.removeChild(node.lastChild);
1580
                    }
1581
                }
1582
1583
                // Insert the new node and add the extracted contents
1584
                insertNodeAtPosition(node, this.startContainer, this.startOffset);
1585
                node.appendChild(content);
1586
1587
                this.selectNode(node);
1588
            },
1589
1590
            cloneRange: function() {
1591
                assertRangeValid(this);
1592
                var range = new Range(getRangeDocument(this));
1593
                var i = rangeProperties.length, prop;
1594
                while (i--) {
1595
                    prop = rangeProperties[i];
1596
                    range[prop] = this[prop];
1597
                }
1598
                return range;
1599
            },
1600
1601
            toString: function() {
1602
                assertRangeValid(this);
1603
                var sc = this.startContainer;
1604
                if (sc === this.endContainer && isCharacterDataNode(sc)) {
1605
                    return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
1606
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1607
                    var textParts = [], iterator = new RangeIterator(this, true);
1608
                    iterateSubtree(iterator, function(node) {
1609
                        // Accept only text or CDATA nodes, not comments
1610
                        if (node.nodeType == 3 || node.nodeType == 4) {
1611
                            textParts.push(node.data);
1612
                        }
1613
                    });
1614
                    iterator.detach();
1615
                    return textParts.join("");
1616
                }
1617
            },
1618
1619
            // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
1620
            // been removed from Mozilla.
1621
1622
            compareNode: function(node) {
1623
                assertRangeValid(this);
1624
1625
                var parent = node.parentNode;
1626
                var nodeIndex = getNodeIndex(node);
1627
1628
                if (!parent) {
1629
                    throw new DOMException("NOT_FOUND_ERR");
1630
                }
1631
1632
                var startComparison = this.comparePoint(parent, nodeIndex),
1633
                    endComparison = this.comparePoint(parent, nodeIndex + 1);
1634
1635
                if (startComparison < 0) { // Node starts before
1636
                    return (endComparison > 0) ? n_b_a : n_b;
1637
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1638
                    return (endComparison > 0) ? n_a : n_i;
1639
                }
1640
            },
1641
1642
            comparePoint: function(node, offset) {
1643
                assertRangeValid(this);
1644
                assertNode(node, "HIERARCHY_REQUEST_ERR");
1645
                assertSameDocumentOrFragment(node, this.startContainer);
1646
1647
                if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
1648
                    return -1;
1649
                } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
1650
                    return 1;
1651
                }
1652
                return 0;
1653
            },
1654
1655
            createContextualFragment: createContextualFragment,
1656
1657
            toHtml: function() {
1658
                return rangeToHtml(this);
1659
            },
1660
1661
            // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
1662
            // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
1663
            intersectsNode: function(node, touchingIsIntersecting) {
1664
                assertRangeValid(this);
1665
                assertNode(node, "NOT_FOUND_ERR");
1666
                if (getDocument(node) !== getRangeDocument(this)) {
1667
                    return false;
1668
                }
1669
1670
                var parent = node.parentNode, offset = getNodeIndex(node);
1671
                assertNode(parent, "NOT_FOUND_ERR");
1672
1673
                var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
1674
                    endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
1675
1676
                return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1677
            },
1678
1679
            isPointInRange: function(node, offset) {
1680
                assertRangeValid(this);
1681
                assertNode(node, "HIERARCHY_REQUEST_ERR");
1682
                assertSameDocumentOrFragment(node, this.startContainer);
1683
1684
                return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
1685
                       (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
1686
            },
1687
1688
            // The methods below are non-standard and invented by me.
1689
1690
            // Sharing a boundary start-to-end or end-to-start does not count as intersection.
1691
            intersectsRange: function(range) {
1692
                return rangesIntersect(this, range, false);
1693
            },
1694
1695
            // Sharing a boundary start-to-end or end-to-start does count as intersection.
1696
            intersectsOrTouchesRange: function(range) {
1697
                return rangesIntersect(this, range, true);
1698
            },
1699
1700
            intersection: function(range) {
1701
                if (this.intersectsRange(range)) {
1702
                    var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
1703
                        endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
1704
1705
                    var intersectionRange = this.cloneRange();
1706
                    if (startComparison == -1) {
1707
                        intersectionRange.setStart(range.startContainer, range.startOffset);
1708
                    }
1709
                    if (endComparison == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing endComparison to 1 using the == operator is not safe. Consider using === instead.
Loading history...
1710
                        intersectionRange.setEnd(range.endContainer, range.endOffset);
1711
                    }
1712
                    return intersectionRange;
1713
                }
1714
                return null;
1715
            },
1716
1717
            union: function(range) {
1718
                if (this.intersectsOrTouchesRange(range)) {
1719
                    var unionRange = this.cloneRange();
1720
                    if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
1721
                        unionRange.setStart(range.startContainer, range.startOffset);
1722
                    }
1723
                    if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing comparePoints(range.endC...tainer, this.endOffset) to 1 using the == operator is not safe. Consider using === instead.
Loading history...
1724
                        unionRange.setEnd(range.endContainer, range.endOffset);
1725
                    }
1726
                    return unionRange;
1727
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1728
                    throw new DOMException("Ranges do not intersect");
1729
                }
1730
            },
1731
1732
            containsNode: function(node, allowPartial) {
1733
                if (allowPartial) {
1734
                    return this.intersectsNode(node, false);
1735
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1736
                    return this.compareNode(node) == n_i;
1737
                }
1738
            },
1739
1740
            containsNodeContents: function(node) {
1741
                return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
1742
            },
1743
1744
            containsRange: function(range) {
1745
                var intersection = this.intersection(range);
1746
                return intersection !== null && range.equals(intersection);
1747
            },
1748
1749
            containsNodeText: function(node) {
1750
                var nodeRange = this.cloneRange();
1751
                nodeRange.selectNode(node);
1752
                var textNodes = nodeRange.getNodes([3]);
1753
                if (textNodes.length > 0) {
1754
                    nodeRange.setStart(textNodes[0], 0);
1755
                    var lastTextNode = textNodes.pop();
1756
                    nodeRange.setEnd(lastTextNode, lastTextNode.length);
1757
                    return this.containsRange(nodeRange);
1758
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1759
                    return this.containsNodeContents(node);
1760
                }
1761
            },
1762
1763
            getNodes: function(nodeTypes, filter) {
1764
                assertRangeValid(this);
1765
                return getNodesInRange(this, nodeTypes, filter);
1766
            },
1767
1768
            getDocument: function() {
1769
                return getRangeDocument(this);
1770
            },
1771
1772
            collapseBefore: function(node) {
1773
                this.setEndBefore(node);
1774
                this.collapse(false);
1775
            },
1776
1777
            collapseAfter: function(node) {
1778
                this.setStartAfter(node);
1779
                this.collapse(true);
1780
            },
1781
            
1782
            getBookmark: function(containerNode) {
1783
                var doc = getRangeDocument(this);
1784
                var preSelectionRange = api.createRange(doc);
1785
                containerNode = containerNode || dom.getBody(doc);
1786
                preSelectionRange.selectNodeContents(containerNode);
1787
                var range = this.intersection(preSelectionRange);
1788
                var start = 0, end = 0;
1789
                if (range) {
1790
                    preSelectionRange.setEnd(range.startContainer, range.startOffset);
1791
                    start = preSelectionRange.toString().length;
1792
                    end = start + range.toString().length;
1793
                }
1794
1795
                return {
1796
                    start: start,
1797
                    end: end,
1798
                    containerNode: containerNode
1799
                };
1800
            },
1801
            
1802
            moveToBookmark: function(bookmark) {
1803
                var containerNode = bookmark.containerNode;
1804
                var charIndex = 0;
1805
                this.setStart(containerNode, 0);
1806
                this.collapse(true);
1807
                var nodeStack = [containerNode], node, foundStart = false, stop = false;
1808
                var nextCharIndex, i, childNodes;
1809
1810
                while (!stop && (node = nodeStack.pop())) {
1811
                    if (node.nodeType == 3) {
1812
                        nextCharIndex = charIndex + node.length;
1813
                        if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
1814
                            this.setStart(node, bookmark.start - charIndex);
1815
                            foundStart = true;
1816
                        }
1817
                        if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
1818
                            this.setEnd(node, bookmark.end - charIndex);
1819
                            stop = true;
1820
                        }
1821
                        charIndex = nextCharIndex;
1822
                    } else {
1823
                        childNodes = node.childNodes;
1824
                        i = childNodes.length;
1825
                        while (i--) {
1826
                            nodeStack.push(childNodes[i]);
1827
                        }
1828
                    }
1829
                }
1830
            },
1831
1832
            getName: function() {
1833
                return "DomRange";
1834
            },
1835
1836
            equals: function(range) {
1837
                return Range.rangesEqual(this, range);
1838
            },
1839
1840
            isValid: function() {
1841
                return isRangeValid(this);
1842
            },
1843
            
1844
            inspect: function() {
1845
                return inspect(this);
1846
            },
1847
            
1848
            detach: function() {
1849
                // In DOM4, detach() is now a no-op.
1850
            }
1851
        });
1852
1853
        function copyComparisonConstantsToObject(obj) {
1854
            obj.START_TO_START = s2s;
1855
            obj.START_TO_END = s2e;
1856
            obj.END_TO_END = e2e;
1857
            obj.END_TO_START = e2s;
1858
1859
            obj.NODE_BEFORE = n_b;
1860
            obj.NODE_AFTER = n_a;
1861
            obj.NODE_BEFORE_AND_AFTER = n_b_a;
1862
            obj.NODE_INSIDE = n_i;
1863
        }
1864
1865
        function copyComparisonConstants(constructor) {
1866
            copyComparisonConstantsToObject(constructor);
1867
            copyComparisonConstantsToObject(constructor.prototype);
1868
        }
1869
1870
        function createRangeContentRemover(remover, boundaryUpdater) {
1871
            return function() {
1872
                assertRangeValid(this);
1873
1874
                var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
1875
1876
                var iterator = new RangeIterator(this, true);
1877
1878
                // Work out where to position the range after content removal
1879
                var node, boundary;
1880
                if (sc !== root) {
1881
                    node = getClosestAncestorIn(sc, root, true);
1882
                    boundary = getBoundaryAfterNode(node);
1883
                    sc = boundary.node;
1884
                    so = boundary.offset;
1885
                }
1886
1887
                // Check none of the range is read-only
1888
                iterateSubtree(iterator, assertNodeNotReadOnly);
1889
1890
                iterator.reset();
1891
1892
                // Remove the content
1893
                var returnValue = remover(iterator);
1894
                iterator.detach();
1895
1896
                // Move to the new position
1897
                boundaryUpdater(this, sc, so, sc, so);
1898
1899
                return returnValue;
1900
            };
1901
        }
1902
1903
        function createPrototypeRange(constructor, boundaryUpdater) {
1904
            function createBeforeAfterNodeSetter(isBefore, isStart) {
1905
                return function(node) {
1906
                    assertValidNodeType(node, beforeAfterNodeTypes);
1907
                    assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
1908
1909
                    var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
1910
                    (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
1911
                };
1912
            }
1913
1914
            function setRangeStart(range, node, offset) {
1915
                var ec = range.endContainer, eo = range.endOffset;
1916
                if (node !== range.startContainer || offset !== range.startOffset) {
1917
                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
1918
                    // is after the current end. In either case, collapse the range to the new position
1919
                    if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing comparePoints(node, offset, ec, eo) to 1 using the == operator is not safe. Consider using === instead.
Loading history...
1920
                        ec = node;
1921
                        eo = offset;
1922
                    }
1923
                    boundaryUpdater(range, node, offset, ec, eo);
1924
                }
1925
            }
1926
1927
            function setRangeEnd(range, node, offset) {
1928
                var sc = range.startContainer, so = range.startOffset;
1929
                if (node !== range.endContainer || offset !== range.endOffset) {
1930
                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
1931
                    // is after the current end. In either case, collapse the range to the new position
1932
                    if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
1933
                        sc = node;
1934
                        so = offset;
1935
                    }
1936
                    boundaryUpdater(range, sc, so, node, offset);
1937
                }
1938
            }
1939
1940
            // Set up inheritance
1941
            var F = function() {};
1942
            F.prototype = api.rangePrototype;
1943
            constructor.prototype = new F();
1944
1945
            util.extend(constructor.prototype, {
1946
                setStart: function(node, offset) {
1947
                    assertNoDocTypeNotationEntityAncestor(node, true);
1948
                    assertValidOffset(node, offset);
1949
1950
                    setRangeStart(this, node, offset);
1951
                },
1952
1953
                setEnd: function(node, offset) {
1954
                    assertNoDocTypeNotationEntityAncestor(node, true);
1955
                    assertValidOffset(node, offset);
1956
1957
                    setRangeEnd(this, node, offset);
1958
                },
1959
1960
                /**
1961
                 * Convenience method to set a range's start and end boundaries. Overloaded as follows:
1962
                 * - Two parameters (node, offset) creates a collapsed range at that position
1963
                 * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
1964
                 *   startOffset and ending at endOffset
1965
                 * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
1966
                 *   startNode and ending at endOffset in endNode
1967
                 */
1968
                setStartAndEnd: function() {
1969
                    var args = arguments;
1970
                    var sc = args[0], so = args[1], ec = sc, eo = so;
1971
1972
                    switch (args.length) {
1973
                        case 3:
1974
                            eo = args[2];
1975
                            break;
1976
                        case 4:
1977
                            ec = args[2];
1978
                            eo = args[3];
1979
                            break;
1980
                    }
1981
1982
                    boundaryUpdater(this, sc, so, ec, eo);
1983
                },
1984
                
1985
                setBoundary: function(node, offset, isStart) {
1986
                    this["set" + (isStart ? "Start" : "End")](node, offset);
1987
                },
1988
1989
                setStartBefore: createBeforeAfterNodeSetter(true, true),
1990
                setStartAfter: createBeforeAfterNodeSetter(false, true),
1991
                setEndBefore: createBeforeAfterNodeSetter(true, false),
1992
                setEndAfter: createBeforeAfterNodeSetter(false, false),
1993
1994
                collapse: function(isStart) {
1995
                    assertRangeValid(this);
1996
                    if (isStart) {
1997
                        boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
1998
                    } else {
1999
                        boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
2000
                    }
2001
                },
2002
2003
                selectNodeContents: function(node) {
2004
                    assertNoDocTypeNotationEntityAncestor(node, true);
2005
2006
                    boundaryUpdater(this, node, 0, node, getNodeLength(node));
2007
                },
2008
2009
                selectNode: function(node) {
2010
                    assertNoDocTypeNotationEntityAncestor(node, false);
2011
                    assertValidNodeType(node, beforeAfterNodeTypes);
2012
2013
                    var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
2014
                    boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
2015
                },
2016
2017
                extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
2018
2019
                deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
2020
2021
                canSurroundContents: function() {
2022
                    assertRangeValid(this);
2023
                    assertNodeNotReadOnly(this.startContainer);
2024
                    assertNodeNotReadOnly(this.endContainer);
2025
2026
                    // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
2027
                    // no non-text nodes.
2028
                    var iterator = new RangeIterator(this, true);
2029
                    var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
2030
                            (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
2031
                    iterator.detach();
2032
                    return !boundariesInvalid;
2033
                },
2034
2035
                splitBoundaries: function() {
2036
                    splitRangeBoundaries(this);
2037
                },
2038
2039
                splitBoundariesPreservingPositions: function(positionsToPreserve) {
2040
                    splitRangeBoundaries(this, positionsToPreserve);
2041
                },
2042
2043
                normalizeBoundaries: function() {
2044
                    assertRangeValid(this);
2045
2046
                    var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
2047
2048
                    var mergeForward = function(node) {
2049
                        var sibling = node.nextSibling;
2050
                        if (sibling && sibling.nodeType == node.nodeType) {
2051
                            ec = node;
2052
                            eo = node.length;
2053
                            node.appendData(sibling.data);
2054
                            sibling.parentNode.removeChild(sibling);
2055
                        }
2056
                    };
2057
2058
                    var mergeBackward = function(node) {
2059
                        var sibling = node.previousSibling;
2060
                        if (sibling && sibling.nodeType == node.nodeType) {
2061
                            sc = node;
2062
                            var nodeLength = node.length;
2063
                            so = sibling.length;
2064
                            node.insertData(0, sibling.data);
2065
                            sibling.parentNode.removeChild(sibling);
2066
                            if (sc == ec) {
2067
                                eo += so;
2068
                                ec = sc;
2069
                            } else if (ec == node.parentNode) {
2070
                                var nodeIndex = getNodeIndex(node);
2071
                                if (eo == nodeIndex) {
2072
                                    ec = node;
2073
                                    eo = nodeLength;
2074
                                } else if (eo > nodeIndex) {
2075
                                    eo--;
2076
                                }
2077
                            }
2078
                        }
2079
                    };
2080
2081
                    var normalizeStart = true;
2082
2083
                    if (isCharacterDataNode(ec)) {
2084
                        if (ec.length == eo) {
2085
                            mergeForward(ec);
2086
                        }
2087
                    } else {
2088
                        if (eo > 0) {
2089
                            var endNode = ec.childNodes[eo - 1];
2090
                            if (endNode && isCharacterDataNode(endNode)) {
2091
                                mergeForward(endNode);
2092
                            }
2093
                        }
2094
                        normalizeStart = !this.collapsed;
2095
                    }
2096
2097
                    if (normalizeStart) {
2098
                        if (isCharacterDataNode(sc)) {
2099
                            if (so == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing so to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2100
                                mergeBackward(sc);
2101
                            }
2102
                        } else {
2103
                            if (so < sc.childNodes.length) {
2104
                                var startNode = sc.childNodes[so];
2105
                                if (startNode && isCharacterDataNode(startNode)) {
2106
                                    mergeBackward(startNode);
2107
                                }
2108
                            }
2109
                        }
2110
                    } else {
2111
                        sc = ec;
2112
                        so = eo;
2113
                    }
2114
2115
                    boundaryUpdater(this, sc, so, ec, eo);
2116
                },
2117
2118
                collapseToPoint: function(node, offset) {
2119
                    assertNoDocTypeNotationEntityAncestor(node, true);
2120
                    assertValidOffset(node, offset);
2121
                    this.setStartAndEnd(node, offset);
2122
                }
2123
            });
2124
2125
            copyComparisonConstants(constructor);
2126
        }
2127
2128
        /*----------------------------------------------------------------------------------------------------------------*/
2129
2130
        // Updates commonAncestorContainer and collapsed after boundary change
2131
        function updateCollapsedAndCommonAncestor(range) {
2132
            range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2133
            range.commonAncestorContainer = range.collapsed ?
2134
                range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
2135
        }
2136
2137
        function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
2138
            range.startContainer = startContainer;
2139
            range.startOffset = startOffset;
2140
            range.endContainer = endContainer;
2141
            range.endOffset = endOffset;
2142
            range.document = dom.getDocument(startContainer);
2143
2144
            updateCollapsedAndCommonAncestor(range);
2145
        }
2146
2147
        function Range(doc) {
2148
            this.startContainer = doc;
2149
            this.startOffset = 0;
2150
            this.endContainer = doc;
2151
            this.endOffset = 0;
2152
            this.document = doc;
2153
            updateCollapsedAndCommonAncestor(this);
2154
        }
2155
2156
        createPrototypeRange(Range, updateBoundaries);
2157
2158
        util.extend(Range, {
2159
            rangeProperties: rangeProperties,
2160
            RangeIterator: RangeIterator,
2161
            copyComparisonConstants: copyComparisonConstants,
2162
            createPrototypeRange: createPrototypeRange,
2163
            inspect: inspect,
2164
            toHtml: rangeToHtml,
2165
            getRangeDocument: getRangeDocument,
2166
            rangesEqual: function(r1, r2) {
2167
                return r1.startContainer === r2.startContainer &&
2168
                    r1.startOffset === r2.startOffset &&
2169
                    r1.endContainer === r2.endContainer &&
2170
                    r1.endOffset === r2.endOffset;
2171
            }
2172
        });
2173
2174
        api.DomRange = Range;
2175
    });
2176
2177
    /*----------------------------------------------------------------------------------------------------------------*/
2178
2179
    // Wrappers for the browser's native DOM Range and/or TextRange implementation 
2180
    api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
2181
        var WrappedRange, WrappedTextRange;
2182
        var dom = api.dom;
2183
        var util = api.util;
2184
        var DomPosition = dom.DomPosition;
2185
        var DomRange = api.DomRange;
2186
        var getBody = dom.getBody;
2187
        var getContentDocument = dom.getContentDocument;
2188
        var isCharacterDataNode = dom.isCharacterDataNode;
2189
2190
2191
        /*----------------------------------------------------------------------------------------------------------------*/
2192
2193
        if (api.features.implementsDomRange) {
2194
            // This is a wrapper around the browser's native DOM Range. It has two aims:
2195
            // - Provide workarounds for specific browser bugs
2196
            // - provide convenient extensions, which are inherited from Rangy's DomRange
2197
2198
            (function() {
2199
                var rangeProto;
2200
                var rangeProperties = DomRange.rangeProperties;
2201
2202
                function updateRangeProperties(range) {
2203
                    var i = rangeProperties.length, prop;
2204
                    while (i--) {
2205
                        prop = rangeProperties[i];
2206
                        range[prop] = range.nativeRange[prop];
2207
                    }
2208
                    // Fix for broken collapsed property in IE 9.
2209
                    range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2210
                }
2211
2212
                function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
2213
                    var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2214
                    var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2215
                    var nativeRangeDifferent = !range.equals(range.nativeRange);
2216
2217
                    // Always set both boundaries for the benefit of IE9 (see issue 35)
2218
                    if (startMoved || endMoved || nativeRangeDifferent) {
2219
                        range.setEnd(endContainer, endOffset);
2220
                        range.setStart(startContainer, startOffset);
2221
                    }
2222
                }
2223
2224
                var createBeforeAfterNodeSetter;
2225
2226
                WrappedRange = function(range) {
2227
                    if (!range) {
2228
                        throw module.createError("WrappedRange: Range must be specified");
2229
                    }
2230
                    this.nativeRange = range;
2231
                    updateRangeProperties(this);
2232
                };
2233
2234
                DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
2235
2236
                rangeProto = WrappedRange.prototype;
2237
2238
                rangeProto.selectNode = function(node) {
2239
                    this.nativeRange.selectNode(node);
2240
                    updateRangeProperties(this);
2241
                };
2242
2243
                rangeProto.cloneContents = function() {
2244
                    return this.nativeRange.cloneContents();
2245
                };
2246
2247
                // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
2248
                // insertNode() is never delegated to the native range.
2249
2250
                rangeProto.surroundContents = function(node) {
2251
                    this.nativeRange.surroundContents(node);
2252
                    updateRangeProperties(this);
2253
                };
2254
2255
                rangeProto.collapse = function(isStart) {
2256
                    this.nativeRange.collapse(isStart);
2257
                    updateRangeProperties(this);
2258
                };
2259
2260
                rangeProto.cloneRange = function() {
2261
                    return new WrappedRange(this.nativeRange.cloneRange());
2262
                };
2263
2264
                rangeProto.refresh = function() {
2265
                    updateRangeProperties(this);
2266
                };
2267
2268
                rangeProto.toString = function() {
2269
                    return this.nativeRange.toString();
2270
                };
2271
2272
                // Create test range and node for feature detection
2273
2274
                var testTextNode = document.createTextNode("test");
2275
                getBody(document).appendChild(testTextNode);
2276
                var range = document.createRange();
2277
2278
                /*--------------------------------------------------------------------------------------------------------*/
2279
2280
                // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2281
                // correct for it
2282
2283
                range.setStart(testTextNode, 0);
2284
                range.setEnd(testTextNode, 0);
2285
2286
                try {
2287
                    range.setStart(testTextNode, 1);
2288
2289
                    rangeProto.setStart = function(node, offset) {
2290
                        this.nativeRange.setStart(node, offset);
2291
                        updateRangeProperties(this);
2292
                    };
2293
2294
                    rangeProto.setEnd = function(node, offset) {
2295
                        this.nativeRange.setEnd(node, offset);
2296
                        updateRangeProperties(this);
2297
                    };
2298
2299
                    createBeforeAfterNodeSetter = function(name) {
2300
                        return function(node) {
2301
                            this.nativeRange[name](node);
2302
                            updateRangeProperties(this);
2303
                        };
2304
                    };
2305
2306
                } catch(ex) {
2307
2308
                    rangeProto.setStart = function(node, offset) {
2309
                        try {
2310
                            this.nativeRange.setStart(node, offset);
2311
                        } catch (ex) {
2312
                            this.nativeRange.setEnd(node, offset);
2313
                            this.nativeRange.setStart(node, offset);
2314
                        }
2315
                        updateRangeProperties(this);
2316
                    };
2317
2318
                    rangeProto.setEnd = function(node, offset) {
2319
                        try {
2320
                            this.nativeRange.setEnd(node, offset);
2321
                        } catch (ex) {
2322
                            this.nativeRange.setStart(node, offset);
2323
                            this.nativeRange.setEnd(node, offset);
2324
                        }
2325
                        updateRangeProperties(this);
2326
                    };
2327
2328
                    createBeforeAfterNodeSetter = function(name, oppositeName) {
2329
                        return function(node) {
2330
                            try {
2331
                                this.nativeRange[name](node);
2332
                            } catch (ex) {
2333
                                this.nativeRange[oppositeName](node);
2334
                                this.nativeRange[name](node);
2335
                            }
2336
                            updateRangeProperties(this);
2337
                        };
2338
                    };
2339
                }
2340
2341
                rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
0 ignored issues
show
Bug introduced by
The call to createBeforeAfterNodeSetter seems to have too many arguments starting with "setEndBefore".
Loading history...
2342
                rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2343
                rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2344
                rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2345
2346
                /*--------------------------------------------------------------------------------------------------------*/
2347
2348
                // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
2349
                // whether the native implementation can be trusted
2350
                rangeProto.selectNodeContents = function(node) {
2351
                    this.setStartAndEnd(node, 0, dom.getNodeLength(node));
2352
                };
2353
2354
                /*--------------------------------------------------------------------------------------------------------*/
2355
2356
                // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
2357
                // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2358
2359
                range.selectNodeContents(testTextNode);
2360
                range.setEnd(testTextNode, 3);
2361
2362
                var range2 = document.createRange();
2363
                range2.selectNodeContents(testTextNode);
2364
                range2.setEnd(testTextNode, 4);
2365
                range2.setStart(testTextNode, 2);
2366
2367
                if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
2368
                        range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing range.compareBoundaryPoi...e.END_TO_START, range2) to 1 using the == operator is not safe. Consider using === instead.
Loading history...
2369
                    // This is the wrong way round, so correct for it
2370
2371
                    rangeProto.compareBoundaryPoints = function(type, range) {
2372
                        range = range.nativeRange || range;
2373
                        if (type == range.START_TO_END) {
2374
                            type = range.END_TO_START;
2375
                        } else if (type == range.END_TO_START) {
2376
                            type = range.START_TO_END;
2377
                        }
2378
                        return this.nativeRange.compareBoundaryPoints(type, range);
2379
                    };
2380
                } else {
2381
                    rangeProto.compareBoundaryPoints = function(type, range) {
2382
                        return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2383
                    };
2384
                }
2385
2386
                /*--------------------------------------------------------------------------------------------------------*/
2387
2388
                // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
2389
2390
                var el = document.createElement("div");
2391
                el.innerHTML = "123";
2392
                var textNode = el.firstChild;
2393
                var body = getBody(document);
2394
                body.appendChild(el);
2395
2396
                range.setStart(textNode, 1);
2397
                range.setEnd(textNode, 2);
2398
                range.deleteContents();
2399
2400
                if (textNode.data == "13") {
2401
                    // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
2402
                    // extractContents()
2403
                    rangeProto.deleteContents = function() {
2404
                        this.nativeRange.deleteContents();
2405
                        updateRangeProperties(this);
2406
                    };
2407
2408
                    rangeProto.extractContents = function() {
2409
                        var frag = this.nativeRange.extractContents();
2410
                        updateRangeProperties(this);
2411
                        return frag;
2412
                    };
2413
                } else {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
2414
                }
2415
2416
                body.removeChild(el);
2417
                body = null;
0 ignored issues
show
Unused Code introduced by
The assignment to body seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
2418
2419
                /*--------------------------------------------------------------------------------------------------------*/
2420
2421
                // Test for existence of createContextualFragment and delegate to it if it exists
2422
                if (util.isHostMethod(range, "createContextualFragment")) {
2423
                    rangeProto.createContextualFragment = function(fragmentStr) {
2424
                        return this.nativeRange.createContextualFragment(fragmentStr);
2425
                    };
2426
                }
2427
2428
                /*--------------------------------------------------------------------------------------------------------*/
2429
2430
                // Clean up
2431
                getBody(document).removeChild(testTextNode);
2432
2433
                rangeProto.getName = function() {
2434
                    return "WrappedRange";
2435
                };
2436
2437
                api.WrappedRange = WrappedRange;
2438
2439
                api.createNativeRange = function(doc) {
2440
                    doc = getContentDocument(doc, module, "createNativeRange");
2441
                    return doc.createRange();
2442
                };
2443
            })();
2444
        }
2445
        
2446
        if (api.features.implementsTextRange) {
2447
            /*
2448
            This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
2449
            method. For example, in the following (where pipes denote the selection boundaries):
2450
2451
            <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
2452
2453
            var range = document.selection.createRange();
2454
            alert(range.parentElement().id); // Should alert "ul" but alerts "b"
2455
2456
            This method returns the common ancestor node of the following:
2457
            - the parentElement() of the textRange
2458
            - the parentElement() of the textRange after calling collapse(true)
2459
            - the parentElement() of the textRange after calling collapse(false)
2460
            */
2461
            var getTextRangeContainerElement = function(textRange) {
2462
                var parentEl = textRange.parentElement();
2463
                var range = textRange.duplicate();
2464
                range.collapse(true);
2465
                var startEl = range.parentElement();
2466
                range = textRange.duplicate();
2467
                range.collapse(false);
2468
                var endEl = range.parentElement();
2469
                var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
2470
2471
                return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
2472
            };
2473
2474
            var textRangeIsCollapsed = function(textRange) {
2475
                return textRange.compareEndPoints("StartToEnd", textRange) == 0;
0 ignored issues
show
Best Practice introduced by
Comparing textRange.compareEndPoin...StartToEnd", textRange) to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2476
            };
2477
2478
            // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
2479
            // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
2480
            // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
2481
            // bugs, handling for inputs and images, plus optimizations.
2482
            var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
2483
                var workingRange = textRange.duplicate();
2484
                workingRange.collapse(isStart);
2485
                var containerElement = workingRange.parentElement();
2486
2487
                // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
2488
                // check for that
2489
                if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
2490
                    containerElement = wholeRangeContainerElement;
2491
                }
2492
2493
2494
                // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
2495
                // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
2496
                if (!containerElement.canHaveHTML) {
2497
                    var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
2498
                    return {
2499
                        boundaryPosition: pos,
2500
                        nodeInfo: {
2501
                            nodeIndex: pos.offset,
2502
                            containerElement: pos.node
2503
                        }
2504
                    };
2505
                }
2506
2507
                var workingNode = dom.getDocument(containerElement).createElement("span");
2508
2509
                // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
2510
                // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
2511
                if (workingNode.parentNode) {
2512
                    workingNode.parentNode.removeChild(workingNode);
2513
                }
2514
2515
                var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
2516
                var previousNode, nextNode, boundaryPosition, boundaryNode;
2517
                var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
2518
                var childNodeCount = containerElement.childNodes.length;
2519
                var end = childNodeCount;
2520
2521
                // Check end first. Code within the loop assumes that the endth child node of the container is definitely
2522
                // after the range boundary.
2523
                var nodeIndex = end;
2524
2525
                while (true) {
2526
                    if (nodeIndex == childNodeCount) {
2527
                        containerElement.appendChild(workingNode);
2528
                    } else {
2529
                        containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
2530
                    }
2531
                    workingRange.moveToElementText(workingNode);
2532
                    comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
2533
                    if (comparison == 0 || start == end) {
0 ignored issues
show
Best Practice introduced by
Comparing comparison to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2534
                        break;
2535
                    } else if (comparison == -1) {
2536
                        if (end == start + 1) {
2537
                            // We know the endth child node is after the range boundary, so we must be done.
2538
                            break;
2539
                        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
2540
                            start = nodeIndex;
2541
                        }
2542
                    } else {
2543
                        end = (end == start + 1) ? start : nodeIndex;
2544
                    }
2545
                    nodeIndex = Math.floor((start + end) / 2);
2546
                    containerElement.removeChild(workingNode);
2547
                }
2548
2549
2550
                // We've now reached or gone past the boundary of the text range we're interested in
2551
                // so have identified the node we want
2552
                boundaryNode = workingNode.nextSibling;
2553
2554
                if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
0 ignored issues
show
Comprehensibility Bug introduced by
The variable comparison does not seem to be initialized in case the while loop on line 2525 is not entered. Are you sure this can never be the case?
Loading history...
2555
                    // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
2556
                    // the node containing the text range's boundary, so we move the end of the working range to the
2557
                    // boundary point and measure the length of its text to get the boundary's offset within the node.
2558
                    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
2559
2560
                    var offset;
2561
2562
                    if (/[\r\n]/.test(boundaryNode.data)) {
2563
                        /*
2564
                        For the particular case of a boundary within a text node containing rendered line breaks (within a
2565
                        <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
2566
                        IE. The facts:
2567
                        
2568
                        - Each line break is represented as \r in the text node's data/nodeValue properties
2569
                        - Each line break is represented as \r\n in the TextRange's 'text' property
2570
                        - The 'text' property of the TextRange does not contain trailing line breaks
2571
                        
2572
                        To get round the problem presented by the final fact above, we can use the fact that TextRange's
2573
                        moveStart() and moveEnd() methods return the actual number of characters moved, which is not
2574
                        necessarily the same as the number of characters it was instructed to move. The simplest approach is
2575
                        to use this to store the characters moved when moving both the start and end of the range to the
2576
                        start of the document body and subtracting the start offset from the end offset (the
2577
                        "move-negative-gazillion" method). However, this is extremely slow when the document is large and
2578
                        the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
2579
                        the end of the document) has the same problem.
2580
                        
2581
                        Another approach that works is to use moveStart() to move the start boundary of the range up to the
2582
                        end boundary one character at a time and incrementing a counter with the value returned by the
2583
                        moveStart() call. However, the check for whether the start boundary has reached the end boundary is
2584
                        expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
2585
                        by the location of the range within the document).
2586
                        
2587
                        The approach used below is a hybrid of the two methods above. It uses the fact that a string
2588
                        containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
2589
                        be longer than the text of the TextRange, so the start of the range is moved that length initially
2590
                        and then a character at a time to make up for any trailing line breaks not contained in the 'text'
2591
                        property. This has good performance in most situations compared to the previous two methods.
2592
                        */
2593
                        var tempRange = workingRange.duplicate();
2594
                        var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
2595
2596
                        offset = tempRange.moveStart("character", rangeLength);
2597
                        while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
0 ignored issues
show
Unused Code introduced by
The assignment to variable comparison seems to be never used. Consider removing it.
Loading history...
2598
                            offset++;
2599
                            tempRange.moveStart("character", 1);
2600
                        }
2601
                    } else {
2602
                        offset = workingRange.text.length;
2603
                    }
2604
                    boundaryPosition = new DomPosition(boundaryNode, offset);
2605
                } else {
2606
2607
                    // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2608
                    // a position within that, and likewise for a start boundary preceding a character data node
2609
                    previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2610
                    nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2611
                    if (nextNode && isCharacterDataNode(nextNode)) {
2612
                        boundaryPosition = new DomPosition(nextNode, 0);
2613
                    } else if (previousNode && isCharacterDataNode(previousNode)) {
2614
                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
2615
                    } else {
2616
                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2617
                    }
2618
                }
2619
2620
                // Clean up
2621
                workingNode.parentNode.removeChild(workingNode);
2622
2623
                return {
2624
                    boundaryPosition: boundaryPosition,
2625
                    nodeInfo: {
2626
                        nodeIndex: nodeIndex,
2627
                        containerElement: containerElement
2628
                    }
2629
                };
2630
            };
2631
2632
            // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
2633
            // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2634
            // (http://code.google.com/p/ierange/)
2635
            var createBoundaryTextRange = function(boundaryPosition, isStart) {
2636
                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2637
                var doc = dom.getDocument(boundaryPosition.node);
2638
                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
2639
                var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
2640
2641
                if (nodeIsDataNode) {
2642
                    boundaryNode = boundaryPosition.node;
2643
                    boundaryParent = boundaryNode.parentNode;
2644
                } else {
2645
                    childNodes = boundaryPosition.node.childNodes;
2646
                    boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2647
                    boundaryParent = boundaryPosition.node;
2648
                }
2649
2650
                // Position the range immediately before the node containing the boundary
2651
                workingNode = doc.createElement("span");
2652
2653
                // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
2654
                // the element rather than immediately before or after it
2655
                workingNode.innerHTML = "&#feff;";
2656
2657
                // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2658
                // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2659
                if (boundaryNode) {
2660
                    boundaryParent.insertBefore(workingNode, boundaryNode);
2661
                } else {
2662
                    boundaryParent.appendChild(workingNode);
2663
                }
2664
2665
                workingRange.moveToElementText(workingNode);
2666
                workingRange.collapse(!isStart);
2667
2668
                // Clean up
2669
                boundaryParent.removeChild(workingNode);
2670
2671
                // Move the working range to the text offset, if required
2672
                if (nodeIsDataNode) {
2673
                    workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2674
                }
2675
2676
                return workingRange;
2677
            };
2678
2679
            /*------------------------------------------------------------------------------------------------------------*/
2680
2681
            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
2682
            // prototype
2683
2684
            WrappedTextRange = function(textRange) {
2685
                this.textRange = textRange;
2686
                this.refresh();
2687
            };
2688
2689
            WrappedTextRange.prototype = new DomRange(document);
2690
2691
            WrappedTextRange.prototype.refresh = function() {
2692
                var start, end, startBoundary;
2693
2694
                // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
2695
                var rangeContainerElement = getTextRangeContainerElement(this.textRange);
2696
2697
                if (textRangeIsCollapsed(this.textRange)) {
2698
                    end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
2699
                        true).boundaryPosition;
2700
                } else {
2701
                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
2702
                    start = startBoundary.boundaryPosition;
2703
2704
                    // An optimization used here is that if the start and end boundaries have the same parent element, the
2705
                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes
2706
                    // the start boundary
2707
                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
2708
                        startBoundary.nodeInfo).boundaryPosition;
2709
                }
2710
2711
                this.setStart(start.node, start.offset);
2712
                this.setEnd(end.node, end.offset);
2713
            };
2714
2715
            WrappedTextRange.prototype.getName = function() {
2716
                return "WrappedTextRange";
2717
            };
2718
2719
            DomRange.copyComparisonConstants(WrappedTextRange);
2720
2721
            var rangeToTextRange = function(range) {
2722
                if (range.collapsed) {
2723
                    return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2724
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
2725
                    var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2726
                    var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
2727
                    var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
2728
                    textRange.setEndPoint("StartToStart", startRange);
2729
                    textRange.setEndPoint("EndToEnd", endRange);
2730
                    return textRange;
2731
                }
2732
            };
2733
2734
            WrappedTextRange.rangeToTextRange = rangeToTextRange;
2735
2736
            WrappedTextRange.prototype.toTextRange = function() {
2737
                return rangeToTextRange(this);
2738
            };
2739
2740
            api.WrappedTextRange = WrappedTextRange;
2741
2742
            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
2743
            // implementation to use by default.
2744
            if (!api.features.implementsDomRange || api.config.preferTextRange) {
2745
                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2746
                var globalObj = (function() { return this; })();
2747
                if (typeof globalObj.Range == "undefined") {
2748
                    globalObj.Range = WrappedTextRange;
2749
                }
2750
2751
                api.createNativeRange = function(doc) {
2752
                    doc = getContentDocument(doc, module, "createNativeRange");
2753
                    return getBody(doc).createTextRange();
2754
                };
2755
2756
                api.WrappedRange = WrappedTextRange;
2757
            }
2758
        }
2759
2760
        api.createRange = function(doc) {
2761
            doc = getContentDocument(doc, module, "createRange");
2762
            return new api.WrappedRange(api.createNativeRange(doc));
2763
        };
2764
2765
        api.createRangyRange = function(doc) {
2766
            doc = getContentDocument(doc, module, "createRangyRange");
2767
            return new DomRange(doc);
2768
        };
2769
2770
        api.createIframeRange = function(iframeEl) {
2771
            module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
2772
            return api.createRange(iframeEl);
2773
        };
2774
2775
        api.createIframeRangyRange = function(iframeEl) {
2776
            module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
2777
            return api.createRangyRange(iframeEl);
2778
        };
2779
2780
        api.addShimListener(function(win) {
2781
            var doc = win.document;
2782
            if (typeof doc.createRange == "undefined") {
2783
                doc.createRange = function() {
2784
                    return api.createRange(doc);
2785
                };
2786
            }
2787
            doc = win = null;
0 ignored issues
show
Unused Code introduced by
The assignment to win seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
2788
        });
2789
    });
2790
2791
    /*----------------------------------------------------------------------------------------------------------------*/
2792
2793
    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
2794
    // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
2795
    api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
2796
        api.config.checkSelectionRanges = true;
2797
2798
        var BOOLEAN = "boolean";
2799
        var NUMBER = "number";
2800
        var dom = api.dom;
2801
        var util = api.util;
2802
        var isHostMethod = util.isHostMethod;
2803
        var DomRange = api.DomRange;
2804
        var WrappedRange = api.WrappedRange;
2805
        var DOMException = api.DOMException;
2806
        var DomPosition = dom.DomPosition;
2807
        var getNativeSelection;
2808
        var selectionIsCollapsed;
2809
        var features = api.features;
2810
        var CONTROL = "Control";
2811
        var getDocument = dom.getDocument;
2812
        var getBody = dom.getBody;
2813
        var rangesEqual = DomRange.rangesEqual;
2814
2815
2816
        // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
2817
        // Boolean (true for backwards).
2818
        function isDirectionBackward(dir) {
2819
            return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
2820
        }
2821
2822
        function getWindow(win, methodName) {
2823
            if (!win) {
2824
                return window;
2825
            } else if (dom.isWindow(win)) {
2826
                return win;
2827
            } else if (win instanceof WrappedSelection) {
2828
                return win.win;
2829
            } else {
2830
                var doc = dom.getContentDocument(win, module, methodName);
2831
                return dom.getWindow(doc);
2832
            }
2833
        }
2834
2835
        function getWinSelection(winParam) {
2836
            return getWindow(winParam, "getWinSelection").getSelection();
2837
        }
2838
2839
        function getDocSelection(winParam) {
2840
            return getWindow(winParam, "getDocSelection").document.selection;
2841
        }
2842
        
2843
        function winSelectionIsBackward(sel) {
2844
            var backward = false;
2845
            if (sel.anchorNode) {
2846
                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
0 ignored issues
show
Best Practice introduced by
Comparing dom.comparePoints(sel.an...sNode, sel.focusOffset) to 1 using the == operator is not safe. Consider using === instead.
Loading history...
2847
            }
2848
            return backward;
2849
        }
2850
2851
        // Test for the Range/TextRange and Selection features required
2852
        // Test for ability to retrieve selection
2853
        var implementsWinGetSelection = isHostMethod(window, "getSelection"),
2854
            implementsDocSelection = util.isHostObject(document, "selection");
2855
2856
        features.implementsWinGetSelection = implementsWinGetSelection;
2857
        features.implementsDocSelection = implementsDocSelection;
2858
2859
        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
2860
2861
        if (useDocumentSelection) {
2862
            getNativeSelection = getDocSelection;
2863
            api.isSelectionValid = function(winParam) {
2864
                var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
2865
2866
                // Check whether the selection TextRange is actually contained within the correct document
2867
                return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
2868
            };
2869
        } else if (implementsWinGetSelection) {
2870
            getNativeSelection = getWinSelection;
2871
            api.isSelectionValid = function() {
2872
                return true;
2873
            };
2874
        } else {
2875
            module.fail("Neither document.selection or window.getSelection() detected.");
2876
        }
2877
2878
        api.getNativeSelection = getNativeSelection;
0 ignored issues
show
Bug introduced by
The variable getNativeSelection seems to not be initialized for all possible execution paths.
Loading history...
2879
2880
        var testSelection = getNativeSelection();
2881
        var testRange = api.createNativeRange(document);
2882
        var body = getBody(document);
2883
2884
        // Obtaining a range from a selection
2885
        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
2886
            ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
2887
2888
        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
2889
2890
        // Test for existence of native selection extend() method
2891
        var selectionHasExtend = isHostMethod(testSelection, "extend");
2892
        features.selectionHasExtend = selectionHasExtend;
2893
        
2894
        // Test if rangeCount exists
2895
        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
2896
        features.selectionHasRangeCount = selectionHasRangeCount;
2897
2898
        var selectionSupportsMultipleRanges = false;
2899
        var collapsedNonEditableSelectionsSupported = true;
2900
2901
        var addRangeBackwardToNative = selectionHasExtend ?
2902
            function(nativeSelection, range) {
2903
                var doc = DomRange.getRangeDocument(range);
2904
                var endRange = api.createRange(doc);
2905
                endRange.collapseToPoint(range.endContainer, range.endOffset);
2906
                nativeSelection.addRange(getNativeRange(endRange));
2907
                nativeSelection.extend(range.startContainer, range.startOffset);
2908
            } : null;
2909
2910
        if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
2911
                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
2912
2913
            (function() {
2914
                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
2915
                // performed on the current document's selection. See issue 109.
2916
2917
                // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
2918
                // because initialization usually happens when the document loads, but could be a problem for a script that
2919
                // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
2920
                // selection.
2921
                var sel = window.getSelection();
2922
                if (sel) {
2923
                    // Store the current selection
2924
                    var originalSelectionRangeCount = sel.rangeCount;
2925
                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
2926
                    var originalSelectionRanges = [];
2927
                    var originalSelectionBackward = winSelectionIsBackward(sel); 
2928
                    for (var i = 0; i < originalSelectionRangeCount; ++i) {
2929
                        originalSelectionRanges[i] = sel.getRangeAt(i);
2930
                    }
2931
                    
2932
                    // Create some test elements
2933
                    var body = getBody(document);
2934
                    var testEl = body.appendChild( document.createElement("div") );
2935
                    testEl.contentEditable = "false";
2936
                    var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
2937
2938
                    // Test whether the native selection will allow a collapsed selection within a non-editable element
2939
                    var r1 = document.createRange();
2940
2941
                    r1.setStart(textNode, 1);
2942
                    r1.collapse(true);
2943
                    sel.addRange(r1);
2944
                    collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
0 ignored issues
show
Best Practice introduced by
Comparing sel.rangeCount to 1 using the == operator is not safe. Consider using === instead.
Loading history...
2945
                    sel.removeAllRanges();
2946
2947
                    // Test whether the native selection is capable of supporting multiple ranges.
2948
                    if (!selectionHasMultipleRanges) {
2949
                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
2950
                        // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
2951
                        // nothing we can do about this while retaining the feature test so we have to resort to a browser
2952
                        // sniff. I'm not happy about it. See
2953
                        // https://code.google.com/p/chromium/issues/detail?id=399791
2954
                        var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
2955
                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
2956
                            selectionSupportsMultipleRanges = false;
2957
                        } else {
2958
                            var r2 = r1.cloneRange();
2959
                            r1.setStart(textNode, 0);
2960
                            r2.setEnd(textNode, 3);
2961
                            r2.setStart(textNode, 2);
2962
                            sel.addRange(r1);
2963
                            sel.addRange(r2);
2964
                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
2965
                        }
2966
                    }
2967
2968
                    // Clean up
2969
                    body.removeChild(testEl);
2970
                    sel.removeAllRanges();
2971
2972
                    for (i = 0; i < originalSelectionRangeCount; ++i) {
2973
                        if (i == 0 && originalSelectionBackward) {
0 ignored issues
show
Best Practice introduced by
Comparing i to 0 using the == operator is not safe. Consider using === instead.
Loading history...
2974
                            if (addRangeBackwardToNative) {
2975
                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);
2976
                            } else {
2977
                                api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
2978
                                sel.addRange(originalSelectionRanges[i]);
2979
                            }
2980
                        } else {
2981
                            sel.addRange(originalSelectionRanges[i]);
2982
                        }
2983
                    }
2984
                }
2985
            })();
2986
        }
2987
2988
        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
2989
        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
2990
2991
        // ControlRanges
2992
        var implementsControlRange = false, testControlRange;
2993
2994
        if (body && isHostMethod(body, "createControlRange")) {
2995
            testControlRange = body.createControlRange();
2996
            if (util.areHostProperties(testControlRange, ["item", "add"])) {
2997
                implementsControlRange = true;
2998
            }
2999
        }
3000
        features.implementsControlRange = implementsControlRange;
3001
3002
        // Selection collapsedness
3003
        if (selectionHasAnchorAndFocus) {
3004
            selectionIsCollapsed = function(sel) {
3005
                return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
3006
            };
3007
        } else {
3008
            selectionIsCollapsed = function(sel) {
3009
                return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
3010
            };
3011
        }
3012
3013
        function updateAnchorAndFocusFromRange(sel, range, backward) {
3014
            var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
3015
            sel.anchorNode = range[anchorPrefix + "Container"];
3016
            sel.anchorOffset = range[anchorPrefix + "Offset"];
3017
            sel.focusNode = range[focusPrefix + "Container"];
3018
            sel.focusOffset = range[focusPrefix + "Offset"];
3019
        }
3020
3021
        function updateAnchorAndFocusFromNativeSelection(sel) {
3022
            var nativeSel = sel.nativeSelection;
3023
            sel.anchorNode = nativeSel.anchorNode;
3024
            sel.anchorOffset = nativeSel.anchorOffset;
3025
            sel.focusNode = nativeSel.focusNode;
3026
            sel.focusOffset = nativeSel.focusOffset;
3027
        }
3028
3029
        function updateEmptySelection(sel) {
3030
            sel.anchorNode = sel.focusNode = null;
3031
            sel.anchorOffset = sel.focusOffset = 0;
3032
            sel.rangeCount = 0;
3033
            sel.isCollapsed = true;
3034
            sel._ranges.length = 0;
3035
        }
3036
3037
        function getNativeRange(range) {
3038
            var nativeRange;
3039
            if (range instanceof DomRange) {
3040
                nativeRange = api.createNativeRange(range.getDocument());
3041
                nativeRange.setEnd(range.endContainer, range.endOffset);
3042
                nativeRange.setStart(range.startContainer, range.startOffset);
3043
            } else if (range instanceof WrappedRange) {
3044
                nativeRange = range.nativeRange;
3045
            } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
3046
                nativeRange = range;
3047
            }
3048
            return nativeRange;
0 ignored issues
show
Bug introduced by
The variable nativeRange does not seem to be initialized in case features.implementsDomRa...e.startContainer).Range on line 3045 is false. Are you sure this can never be the case?
Loading history...
3049
        }
3050
3051
        function rangeContainsSingleElement(rangeNodes) {
3052
            if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
0 ignored issues
show
Best Practice introduced by
Comparing rangeNodes.0.nodeType to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
3053
                return false;
3054
            }
3055
            for (var i = 1, len = rangeNodes.length; i < len; ++i) {
3056
                if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
3057
                    return false;
3058
                }
3059
            }
3060
            return true;
3061
        }
3062
3063
        function getSingleElementFromRange(range) {
3064
            var nodes = range.getNodes();
3065
            if (!rangeContainsSingleElement(nodes)) {
3066
                throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
3067
            }
3068
            return nodes[0];
3069
        }
3070
3071
        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
3072
        function isTextRange(range) {
3073
            return !!range && typeof range.text != "undefined";
3074
        }
3075
3076
        function updateFromTextRange(sel, range) {
3077
            // Create a Range from the selected TextRange
3078
            var wrappedRange = new WrappedRange(range);
3079
            sel._ranges = [wrappedRange];
3080
3081
            updateAnchorAndFocusFromRange(sel, wrappedRange, false);
3082
            sel.rangeCount = 1;
3083
            sel.isCollapsed = wrappedRange.collapsed;
3084
        }
3085
3086
        function updateControlSelection(sel) {
3087
            // Update the wrapped selection based on what's now in the native selection
3088
            sel._ranges.length = 0;
3089
            if (sel.docSelection.type == "None") {
3090
                updateEmptySelection(sel);
3091
            } else {
3092
                var controlRange = sel.docSelection.createRange();
3093
                if (isTextRange(controlRange)) {
3094
                    // This case (where the selection type is "Control" and calling createRange() on the selection returns
3095
                    // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
3096
                    // ControlRange have been removed from the ControlRange and removed from the document.
3097
                    updateFromTextRange(sel, controlRange);
3098
                } else {
3099
                    sel.rangeCount = controlRange.length;
3100
                    var range, doc = getDocument(controlRange.item(0));
3101
                    for (var i = 0; i < sel.rangeCount; ++i) {
3102
                        range = api.createRange(doc);
3103
                        range.selectNode(controlRange.item(i));
3104
                        sel._ranges.push(range);
3105
                    }
3106
                    sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
0 ignored issues
show
Best Practice introduced by
Comparing sel.rangeCount to 1 using the == operator is not safe. Consider using === instead.
Loading history...
3107
                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
3108
                }
3109
            }
3110
        }
3111
3112
        function addRangeToControlSelection(sel, range) {
3113
            var controlRange = sel.docSelection.createRange();
3114
            var rangeElement = getSingleElementFromRange(range);
3115
3116
            // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
3117
            // contained by the supplied range
3118
            var doc = getDocument(controlRange.item(0));
3119
            var newControlRange = getBody(doc).createControlRange();
3120
            for (var i = 0, len = controlRange.length; i < len; ++i) {
3121
                newControlRange.add(controlRange.item(i));
3122
            }
3123
            try {
3124
                newControlRange.add(rangeElement);
3125
            } catch (ex) {
3126
                throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
3127
            }
3128
            newControlRange.select();
3129
3130
            // Update the wrapped selection based on what's now in the native selection
3131
            updateControlSelection(sel);
3132
        }
3133
3134
        var getSelectionRangeAt;
3135
3136
        if (isHostMethod(testSelection, "getRangeAt")) {
3137
            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
3138
            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
3139
            // lesson to us all, especially me.
3140
            getSelectionRangeAt = function(sel, index) {
3141
                try {
3142
                    return sel.getRangeAt(index);
3143
                } catch (ex) {
3144
                    return null;
3145
                }
3146
            };
3147
        } else if (selectionHasAnchorAndFocus) {
3148
            getSelectionRangeAt = function(sel) {
3149
                var doc = getDocument(sel.anchorNode);
3150
                var range = api.createRange(doc);
3151
                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
3152
3153
                // Handle the case when the selection was selected backwards (from the end to the start in the
3154
                // document)
3155
                if (range.collapsed !== this.isCollapsed) {
3156
                    range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
3157
                }
3158
3159
                return range;
3160
            };
3161
        }
3162
3163
        function WrappedSelection(selection, docSelection, win) {
3164
            this.nativeSelection = selection;
3165
            this.docSelection = docSelection;
3166
            this._ranges = [];
3167
            this.win = win;
3168
            this.refresh();
3169
        }
3170
3171
        WrappedSelection.prototype = api.selectionPrototype;
3172
3173
        function deleteProperties(sel) {
3174
            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
3175
            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
3176
            sel.detached = true;
3177
        }
3178
3179
        var cachedRangySelections = [];
3180
3181
        function actOnCachedSelection(win, action) {
3182
            var i = cachedRangySelections.length, cached, sel;
3183
            while (i--) {
3184
                cached = cachedRangySelections[i];
3185
                sel = cached.selection;
3186
                if (action == "deleteAll") {
3187
                    deleteProperties(sel);
3188
                } else if (cached.win == win) {
3189
                    if (action == "delete") {
3190
                        cachedRangySelections.splice(i, 1);
3191
                        return true;
3192
                    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3193
                        return sel;
3194
                    }
3195
                }
3196
            }
3197
            if (action == "deleteAll") {
3198
                cachedRangySelections.length = 0;
3199
            }
3200
            return null;
3201
        }
3202
3203
        var getSelection = function(win) {
3204
            // Check if the parameter is a Rangy Selection object
3205
            if (win && win instanceof WrappedSelection) {
3206
                win.refresh();
3207
                return win;
3208
            }
3209
3210
            win = getWindow(win, "getNativeSelection");
3211
3212
            var sel = actOnCachedSelection(win);
3213
            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
3214
            if (sel) {
3215
                sel.nativeSelection = nativeSel;
3216
                sel.docSelection = docSel;
3217
                sel.refresh();
3218
            } else {
3219
                sel = new WrappedSelection(nativeSel, docSel, win);
3220
                cachedRangySelections.push( { win: win, selection: sel } );
3221
            }
3222
            return sel;
3223
        };
3224
3225
        api.getSelection = getSelection;
3226
3227
        api.getIframeSelection = function(iframeEl) {
3228
            module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
3229
            return api.getSelection(dom.getIframeWindow(iframeEl));
3230
        };
3231
3232
        var selProto = WrappedSelection.prototype;
3233
3234
        function createControlSelection(sel, ranges) {
3235
            // Ensure that the selection becomes of type "Control"
3236
            var doc = getDocument(ranges[0].startContainer);
3237
            var controlRange = getBody(doc).createControlRange();
3238
            for (var i = 0, el, len = ranges.length; i < len; ++i) {
3239
                el = getSingleElementFromRange(ranges[i]);
3240
                try {
3241
                    controlRange.add(el);
3242
                } catch (ex) {
3243
                    throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
3244
                }
3245
            }
3246
            controlRange.select();
3247
3248
            // Update the wrapped selection based on what's now in the native selection
3249
            updateControlSelection(sel);
3250
        }
3251
3252
        // Selecting a range
3253
        if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
3254
            selProto.removeAllRanges = function() {
3255
                this.nativeSelection.removeAllRanges();
3256
                updateEmptySelection(this);
3257
            };
3258
3259
            var addRangeBackward = function(sel, range) {
3260
                addRangeBackwardToNative(sel.nativeSelection, range);
3261
                sel.refresh();
3262
            };
3263
3264
            if (selectionHasRangeCount) {
3265
                selProto.addRange = function(range, direction) {
3266
                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3267
                        addRangeToControlSelection(this, range);
3268
                    } else {
3269
                        if (isDirectionBackward(direction) && selectionHasExtend) {
3270
                            addRangeBackward(this, range);
3271
                        } else {
3272
                            var previousRangeCount;
3273
                            if (selectionSupportsMultipleRanges) {
3274
                                previousRangeCount = this.rangeCount;
3275
                            } else {
3276
                                this.removeAllRanges();
3277
                                previousRangeCount = 0;
3278
                            }
3279
                            // Clone the native range so that changing the selected range does not affect the selection.
3280
                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See
3281
                            // issue 80.
3282
                            this.nativeSelection.addRange(getNativeRange(range).cloneRange());
3283
3284
                            // Check whether adding the range was successful
3285
                            this.rangeCount = this.nativeSelection.rangeCount;
3286
3287
                            if (this.rangeCount == previousRangeCount + 1) {
3288
                                // The range was added successfully
3289
3290
                                // Check whether the range that we added to the selection is reflected in the last range extracted from
3291
                                // the selection
3292
                                if (api.config.checkSelectionRanges) {
3293
                                    var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
0 ignored issues
show
Bug introduced by
The call to getSelectionRangeAt seems to have too many arguments starting with this.rangeCount - 1.
Loading history...
3294
                                    if (nativeRange && !rangesEqual(nativeRange, range)) {
3295
                                        // Happens in WebKit with, for example, a selection placed at the start of a text node
3296
                                        range = new WrappedRange(nativeRange);
3297
                                    }
3298
                                }
3299
                                this._ranges[this.rangeCount - 1] = range;
3300
                                updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
3301
                                this.isCollapsed = selectionIsCollapsed(this);
3302
                            } else {
3303
                                // The range was not added successfully. The simplest thing is to refresh
3304
                                this.refresh();
3305
                            }
3306
                        }
3307
                    }
3308
                };
3309
            } else {
3310
                selProto.addRange = function(range, direction) {
3311
                    if (isDirectionBackward(direction) && selectionHasExtend) {
3312
                        addRangeBackward(this, range);
3313
                    } else {
3314
                        this.nativeSelection.addRange(getNativeRange(range));
3315
                        this.refresh();
3316
                    }
3317
                };
3318
            }
3319
3320
            selProto.setRanges = function(ranges) {
3321
                if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
3322
                    createControlSelection(this, ranges);
3323
                } else {
3324
                    this.removeAllRanges();
3325
                    for (var i = 0, len = ranges.length; i < len; ++i) {
3326
                        this.addRange(ranges[i]);
3327
                    }
3328
                }
3329
            };
3330
        } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
3331
                   implementsControlRange && useDocumentSelection) {
3332
3333
            selProto.removeAllRanges = function() {
3334
                // Added try/catch as fix for issue #21
3335
                try {
3336
                    this.docSelection.empty();
3337
3338
                    // Check for empty() not working (issue #24)
3339
                    if (this.docSelection.type != "None") {
3340
                        // Work around failure to empty a control selection by instead selecting a TextRange and then
3341
                        // calling empty()
3342
                        var doc;
3343
                        if (this.anchorNode) {
3344
                            doc = getDocument(this.anchorNode);
3345
                        } else if (this.docSelection.type == CONTROL) {
3346
                            var controlRange = this.docSelection.createRange();
3347
                            if (controlRange.length) {
3348
                                doc = getDocument( controlRange.item(0) );
3349
                            }
3350
                        }
3351
                        if (doc) {
3352
                            var textRange = getBody(doc).createTextRange();
3353
                            textRange.select();
3354
                            this.docSelection.empty();
3355
                        }
3356
                    }
3357
                } catch(ex) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
3358
                updateEmptySelection(this);
3359
            };
3360
3361
            selProto.addRange = function(range) {
3362
                if (this.docSelection.type == CONTROL) {
3363
                    addRangeToControlSelection(this, range);
3364
                } else {
3365
                    api.WrappedTextRange.rangeToTextRange(range).select();
3366
                    this._ranges[0] = range;
3367
                    this.rangeCount = 1;
3368
                    this.isCollapsed = this._ranges[0].collapsed;
3369
                    updateAnchorAndFocusFromRange(this, range, false);
3370
                }
3371
            };
3372
3373
            selProto.setRanges = function(ranges) {
3374
                this.removeAllRanges();
3375
                var rangeCount = ranges.length;
3376
                if (rangeCount > 1) {
3377
                    createControlSelection(this, ranges);
3378
                } else if (rangeCount) {
3379
                    this.addRange(ranges[0]);
3380
                }
3381
            };
3382
        } else {
3383
            module.fail("No means of selecting a Range or TextRange was found");
3384
            return false;
3385
        }
3386
3387
        selProto.getRangeAt = function(index) {
3388
            if (index < 0 || index >= this.rangeCount) {
3389
                throw new DOMException("INDEX_SIZE_ERR");
3390
            } else {
3391
                // Clone the range to preserve selection-range independence. See issue 80.
3392
                return this._ranges[index].cloneRange();
3393
            }
3394
        };
3395
3396
        var refreshSelection;
3397
3398
        if (useDocumentSelection) {
3399
            refreshSelection = function(sel) {
3400
                var range;
3401
                if (api.isSelectionValid(sel.win)) {
3402
                    range = sel.docSelection.createRange();
3403
                } else {
3404
                    range = getBody(sel.win.document).createTextRange();
3405
                    range.collapse(true);
3406
                }
3407
3408
                if (sel.docSelection.type == CONTROL) {
3409
                    updateControlSelection(sel);
3410
                } else if (isTextRange(range)) {
3411
                    updateFromTextRange(sel, range);
3412
                } else {
3413
                    updateEmptySelection(sel);
3414
                }
3415
            };
3416
        } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
3417
            refreshSelection = function(sel) {
3418
                if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
3419
                    updateControlSelection(sel);
3420
                } else {
3421
                    sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
3422
                    if (sel.rangeCount) {
3423
                        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3424
                            sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
3425
                        }
3426
                        updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
3427
                        sel.isCollapsed = selectionIsCollapsed(sel);
3428
                    } else {
3429
                        updateEmptySelection(sel);
3430
                    }
3431
                }
3432
            };
3433
        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
3434
            refreshSelection = function(sel) {
3435
                var range, nativeSel = sel.nativeSelection;
3436
                if (nativeSel.anchorNode) {
3437
                    range = getSelectionRangeAt(nativeSel, 0);
0 ignored issues
show
Bug introduced by
The call to getSelectionRangeAt seems to have too many arguments starting with 0.
Loading history...
3438
                    sel._ranges = [range];
3439
                    sel.rangeCount = 1;
3440
                    updateAnchorAndFocusFromNativeSelection(sel);
3441
                    sel.isCollapsed = selectionIsCollapsed(sel);
3442
                } else {
3443
                    updateEmptySelection(sel);
3444
                }
3445
            };
3446
        } else {
3447
            module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
3448
            return false;
3449
        }
3450
3451
        selProto.refresh = function(checkForChanges) {
3452
            var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
3453
            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
3454
3455
            refreshSelection(this);
3456
            if (checkForChanges) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if checkForChanges is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
3457
                // Check the range count first
3458
                var i = oldRanges.length;
3459
                if (i != this._ranges.length) {
3460
                    return true;
3461
                }
3462
3463
                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
3464
                // ranges after this
3465
                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
3466
                    return true;
3467
                }
3468
3469
                // Finally, compare each range in turn
3470
                while (i--) {
3471
                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {
3472
                        return true;
3473
                    }
3474
                }
3475
                return false;
3476
            }
3477
        };
3478
3479
        // Removal of a single range
3480
        var removeRangeManually = function(sel, range) {
3481
            var ranges = sel.getAllRanges();
3482
            sel.removeAllRanges();
3483
            for (var i = 0, len = ranges.length; i < len; ++i) {
3484
                if (!rangesEqual(range, ranges[i])) {
3485
                    sel.addRange(ranges[i]);
3486
                }
3487
            }
3488
            if (!sel.rangeCount) {
3489
                updateEmptySelection(sel);
3490
            }
3491
        };
3492
3493
        if (implementsControlRange && implementsDocSelection) {
3494
            selProto.removeRange = function(range) {
3495
                if (this.docSelection.type == CONTROL) {
3496
                    var controlRange = this.docSelection.createRange();
3497
                    var rangeElement = getSingleElementFromRange(range);
3498
3499
                    // Create a new ControlRange containing all the elements in the selected ControlRange minus the
3500
                    // element contained by the supplied range
3501
                    var doc = getDocument(controlRange.item(0));
3502
                    var newControlRange = getBody(doc).createControlRange();
3503
                    var el, removed = false;
3504
                    for (var i = 0, len = controlRange.length; i < len; ++i) {
3505
                        el = controlRange.item(i);
3506
                        if (el !== rangeElement || removed) {
3507
                            newControlRange.add(controlRange.item(i));
3508
                        } else {
3509
                            removed = true;
3510
                        }
3511
                    }
3512
                    newControlRange.select();
3513
3514
                    // Update the wrapped selection based on what's now in the native selection
3515
                    updateControlSelection(this);
3516
                } else {
3517
                    removeRangeManually(this, range);
3518
                }
3519
            };
3520
        } else {
3521
            selProto.removeRange = function(range) {
3522
                removeRangeManually(this, range);
3523
            };
3524
        }
3525
3526
        // Detecting if a selection is backward
3527
        var selectionIsBackward;
3528
        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
3529
            selectionIsBackward = winSelectionIsBackward;
3530
3531
            selProto.isBackward = function() {
3532
                return selectionIsBackward(this);
3533
            };
3534
        } else {
3535
            selectionIsBackward = selProto.isBackward = function() {
3536
                return false;
3537
            };
3538
        }
3539
3540
        // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
3541
        selProto.isBackwards = selProto.isBackward;
3542
3543
        // Selection stringifier
3544
        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
3545
        // The current spec does not yet define this method.
3546
        selProto.toString = function() {
3547
            var rangeTexts = [];
3548
            for (var i = 0, len = this.rangeCount; i < len; ++i) {
3549
                rangeTexts[i] = "" + this._ranges[i];
3550
            }
3551
            return rangeTexts.join("");
3552
        };
3553
3554
        function assertNodeInSameDocument(sel, node) {
3555
            if (sel.win.document != getDocument(node)) {
3556
                throw new DOMException("WRONG_DOCUMENT_ERR");
3557
            }
3558
        }
3559
3560
        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
3561
        selProto.collapse = function(node, offset) {
3562
            assertNodeInSameDocument(this, node);
3563
            var range = api.createRange(node);
3564
            range.collapseToPoint(node, offset);
3565
            this.setSingleRange(range);
3566
            this.isCollapsed = true;
3567
        };
3568
3569
        selProto.collapseToStart = function() {
3570
            if (this.rangeCount) {
3571
                var range = this._ranges[0];
3572
                this.collapse(range.startContainer, range.startOffset);
3573
            } else {
3574
                throw new DOMException("INVALID_STATE_ERR");
3575
            }
3576
        };
3577
3578
        selProto.collapseToEnd = function() {
3579
            if (this.rangeCount) {
3580
                var range = this._ranges[this.rangeCount - 1];
3581
                this.collapse(range.endContainer, range.endOffset);
3582
            } else {
3583
                throw new DOMException("INVALID_STATE_ERR");
3584
            }
3585
        };
3586
3587
        // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
3588
        // never used by Rangy.
3589
        selProto.selectAllChildren = function(node) {
3590
            assertNodeInSameDocument(this, node);
3591
            var range = api.createRange(node);
3592
            range.selectNodeContents(node);
3593
            this.setSingleRange(range);
3594
        };
3595
3596
        selProto.deleteFromDocument = function() {
3597
            // Sepcial behaviour required for IE's control selections
3598
            if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3599
                var controlRange = this.docSelection.createRange();
3600
                var element;
3601
                while (controlRange.length) {
3602
                    element = controlRange.item(0);
3603
                    controlRange.remove(element);
3604
                    element.parentNode.removeChild(element);
3605
                }
3606
                this.refresh();
3607
            } else if (this.rangeCount) {
3608
                var ranges = this.getAllRanges();
3609
                if (ranges.length) {
3610
                    this.removeAllRanges();
3611
                    for (var i = 0, len = ranges.length; i < len; ++i) {
3612
                        ranges[i].deleteContents();
3613
                    }
3614
                    // The spec says nothing about what the selection should contain after calling deleteContents on each
3615
                    // range. Firefox moves the selection to where the final selected range was, so we emulate that
3616
                    this.addRange(ranges[len - 1]);
3617
                }
3618
            }
3619
        };
3620
3621
        // The following are non-standard extensions
3622
        selProto.eachRange = function(func, returnValue) {
3623
            for (var i = 0, len = this._ranges.length; i < len; ++i) {
3624
                if ( func( this.getRangeAt(i) ) ) {
3625
                    return returnValue;
3626
                }
3627
            }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3628
        };
3629
3630
        selProto.getAllRanges = function() {
3631
            var ranges = [];
3632
            this.eachRange(function(range) {
3633
                ranges.push(range);
3634
            });
3635
            return ranges;
3636
        };
3637
3638
        selProto.setSingleRange = function(range, direction) {
3639
            this.removeAllRanges();
3640
            this.addRange(range, direction);
3641
        };
3642
3643
        selProto.callMethodOnEachRange = function(methodName, params) {
3644
            var results = [];
3645
            this.eachRange( function(range) {
3646
                results.push( range[methodName].apply(range, params) );
3647
            } );
3648
            return results;
3649
        };
3650
        
3651
        function createStartOrEndSetter(isStart) {
3652
            return function(node, offset) {
3653
                var range;
3654
                if (this.rangeCount) {
3655
                    range = this.getRangeAt(0);
3656
                    range["set" + (isStart ? "Start" : "End")](node, offset);
3657
                } else {
3658
                    range = api.createRange(this.win.document);
3659
                    range.setStartAndEnd(node, offset);
3660
                }
3661
                this.setSingleRange(range, this.isBackward());
3662
            };
3663
        }
3664
3665
        selProto.setStart = createStartOrEndSetter(true);
3666
        selProto.setEnd = createStartOrEndSetter(false);
3667
        
3668
        // Add select() method to Range prototype. Any existing selection will be removed.
3669
        api.rangePrototype.select = function(direction) {
3670
            getSelection( this.getDocument() ).setSingleRange(this, direction);
3671
        };
3672
3673
        selProto.changeEachRange = function(func) {
3674
            var ranges = [];
3675
            var backward = this.isBackward();
3676
3677
            this.eachRange(function(range) {
3678
                func(range);
3679
                ranges.push(range);
3680
            });
3681
3682
            this.removeAllRanges();
3683
            if (backward && ranges.length == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing ranges.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
3684
                this.addRange(ranges[0], "backward");
3685
            } else {
3686
                this.setRanges(ranges);
3687
            }
3688
        };
3689
3690
        selProto.containsNode = function(node, allowPartial) {
3691
            return this.eachRange( function(range) {
3692
                return range.containsNode(node, allowPartial);
3693
            }, true ) || false;
3694
        };
3695
3696
        selProto.getBookmark = function(containerNode) {
3697
            return {
3698
                backward: this.isBackward(),
3699
                rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
3700
            };
3701
        };
3702
3703
        selProto.moveToBookmark = function(bookmark) {
3704
            var selRanges = [];
3705
            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
3706
                range = api.createRange(this.win);
3707
                range.moveToBookmark(rangeBookmark);
3708
                selRanges.push(range);
3709
            }
3710
            if (bookmark.backward) {
3711
                this.setSingleRange(selRanges[0], "backward");
3712
            } else {
3713
                this.setRanges(selRanges);
3714
            }
3715
        };
3716
3717
        selProto.toHtml = function() {
3718
            var rangeHtmls = [];
3719
            this.eachRange(function(range) {
3720
                rangeHtmls.push( DomRange.toHtml(range) );
3721
            });
3722
            return rangeHtmls.join("");
3723
        };
3724
3725
        if (features.implementsTextRange) {
3726
            selProto.getNativeTextRange = function() {
3727
                var sel, textRange;
0 ignored issues
show
Unused Code introduced by
The variable textRange seems to be never used. Consider removing it.
Loading history...
3728
                if ( (sel = this.docSelection) ) {
3729
                    var range = sel.createRange();
3730
                    if (isTextRange(range)) {
3731
                        return range;
3732
                    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3733
                        throw module.createError("getNativeTextRange: selection is a control selection"); 
3734
                    }
3735
                } else if (this.rangeCount > 0) {
3736
                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
3737
                } else {
3738
                    throw module.createError("getNativeTextRange: selection contains no range");
3739
                }
3740
            };
3741
        }
3742
3743
        function inspect(sel) {
3744
            var rangeInspects = [];
3745
            var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
3746
            var focus = new DomPosition(sel.focusNode, sel.focusOffset);
3747
            var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
3748
3749
            if (typeof sel.rangeCount != "undefined") {
3750
                for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3751
                    rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
3752
                }
3753
            }
3754
            return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
3755
                    ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
3756
        }
3757
3758
        selProto.getName = function() {
3759
            return "WrappedSelection";
3760
        };
3761
3762
        selProto.inspect = function() {
3763
            return inspect(this);
3764
        };
3765
3766
        selProto.detach = function() {
3767
            actOnCachedSelection(this.win, "delete");
3768
            deleteProperties(this);
3769
        };
3770
3771
        WrappedSelection.detachAll = function() {
3772
            actOnCachedSelection(null, "deleteAll");
3773
        };
3774
3775
        WrappedSelection.inspect = inspect;
3776
        WrappedSelection.isDirectionBackward = isDirectionBackward;
3777
3778
        api.Selection = WrappedSelection;
3779
3780
        api.selectionPrototype = selProto;
3781
3782
        api.addShimListener(function(win) {
3783
            if (typeof win.getSelection == "undefined") {
3784
                win.getSelection = function() {
3785
                    return getSelection(win);
3786
                };
3787
            }
3788
            win = null;
3789
        });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3790
    });
3791
    
3792
3793
    /*----------------------------------------------------------------------------------------------------------------*/
3794
3795
    return api;
3796
}, this);;/**
3797
 * Selection save and restore module for Rangy.
3798
 * Saves and restores user selections using marker invisible elements in the DOM.
3799
 *
3800
 * Part of Rangy, a cross-browser JavaScript range and selection library
3801
 * http://code.google.com/p/rangy/
3802
 *
3803
 * Depends on Rangy core.
3804
 *
3805
 * Copyright 2014, Tim Down
3806
 * Licensed under the MIT license.
3807
 * Version: 1.3alpha.20140804
3808
 * Build date: 4 August 2014
3809
 */
3810
(function(factory, global) {
3811
    if (typeof define == "function" && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
3812
        // AMD. Register as an anonymous module with a dependency on Rangy.
3813
        define(["rangy"], factory);
3814
        /*
3815
         } else if (typeof exports == "object") {
3816
         // Node/CommonJS style for Browserify
3817
         module.exports = factory;
3818
         */
3819
    } else {
3820
        // No AMD or CommonJS support so we use the rangy global variable
3821
        factory(global.rangy);
3822
    }
3823
})(function(rangy) {
3824
    rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
3825
        var dom = api.dom;
3826
3827
        var markerTextChar = "\ufeff";
3828
3829
        function gEBI(id, doc) {
3830
            return (doc || document).getElementById(id);
3831
        }
3832
3833
        function insertRangeBoundaryMarker(range, atStart) {
3834
            var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
3835
            var markerEl;
3836
            var doc = dom.getDocument(range.startContainer);
3837
3838
            // Clone the Range and collapse to the appropriate boundary point
3839
            var boundaryRange = range.cloneRange();
3840
            boundaryRange.collapse(atStart);
3841
3842
            // Create the marker element containing a single invisible character using DOM methods and insert it
3843
            markerEl = doc.createElement("span");
3844
            markerEl.id = markerId;
3845
            markerEl.style.lineHeight = "0";
3846
            markerEl.style.display = "none";
3847
            markerEl.className = "rangySelectionBoundary";
3848
            markerEl.appendChild(doc.createTextNode(markerTextChar));
3849
3850
            boundaryRange.insertNode(markerEl);
3851
            return markerEl;
3852
        }
3853
3854
        function setRangeBoundary(doc, range, markerId, atStart) {
3855
            var markerEl = gEBI(markerId, doc);
3856
            if (markerEl) {
3857
                range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
3858
                markerEl.parentNode.removeChild(markerEl);
3859
            } else {
3860
                module.warn("Marker element has been removed. Cannot restore selection.");
3861
            }
3862
        }
3863
3864
        function compareRanges(r1, r2) {
3865
            return r2.compareBoundaryPoints(r1.START_TO_START, r1);
3866
        }
3867
3868
        function saveRange(range, backward) {
3869
            var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
3870
3871
            if (range.collapsed) {
3872
                endEl = insertRangeBoundaryMarker(range, false);
3873
                return {
3874
                    document: doc,
3875
                    markerId: endEl.id,
3876
                    collapsed: true
3877
                };
3878
            } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
3879
                endEl = insertRangeBoundaryMarker(range, false);
3880
                startEl = insertRangeBoundaryMarker(range, true);
3881
3882
                return {
3883
                    document: doc,
3884
                    startMarkerId: startEl.id,
3885
                    endMarkerId: endEl.id,
3886
                    collapsed: false,
3887
                    backward: backward,
3888
                    toString: function() {
3889
                        return "original text: '" + text + "', new text: '" + range.toString() + "'";
3890
                    }
3891
                };
3892
            }
3893
        }
3894
3895
        function restoreRange(rangeInfo, normalize) {
3896
            var doc = rangeInfo.document;
3897
            if (typeof normalize == "undefined") {
3898
                normalize = true;
3899
            }
3900
            var range = api.createRange(doc);
3901
            if (rangeInfo.collapsed) {
3902
                var markerEl = gEBI(rangeInfo.markerId, doc);
3903
                if (markerEl) {
3904
                    markerEl.style.display = "inline";
3905
                    var previousNode = markerEl.previousSibling;
3906
3907
                    // Workaround for issue 17
3908
                    if (previousNode && previousNode.nodeType == 3) {
3909
                        markerEl.parentNode.removeChild(markerEl);
3910
                        range.collapseToPoint(previousNode, previousNode.length);
3911
                    } else {
3912
                        range.collapseBefore(markerEl);
3913
                        markerEl.parentNode.removeChild(markerEl);
3914
                    }
3915
                } else {
3916
                    module.warn("Marker element has been removed. Cannot restore selection.");
3917
                }
3918
            } else {
3919
                setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
3920
                setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
3921
            }
3922
3923
            if (normalize) {
3924
                range.normalizeBoundaries();
3925
            }
3926
3927
            return range;
3928
        }
3929
3930
        function saveRanges(ranges, backward) {
3931
            var rangeInfos = [], range, doc;
3932
3933
            // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
3934
            ranges = ranges.slice(0);
3935
            ranges.sort(compareRanges);
3936
3937
            for (var i = 0, len = ranges.length; i < len; ++i) {
3938
                rangeInfos[i] = saveRange(ranges[i], backward);
3939
            }
3940
3941
            // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
3942
            // between its markers
3943
            for (i = len - 1; i >= 0; --i) {
3944
                range = ranges[i];
3945
                doc = api.DomRange.getRangeDocument(range);
3946
                if (range.collapsed) {
3947
                    range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
3948
                } else {
3949
                    range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
3950
                    range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
3951
                }
3952
            }
3953
3954
            return rangeInfos;
3955
        }
3956
3957
        function saveSelection(win) {
3958
            if (!api.isSelectionValid(win)) {
3959
                module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
3960
                return null;
3961
            }
3962
            var sel = api.getSelection(win);
3963
            var ranges = sel.getAllRanges();
3964
            var backward = (ranges.length == 1 && sel.isBackward());
0 ignored issues
show
Best Practice introduced by
Comparing ranges.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
3965
3966
            var rangeInfos = saveRanges(ranges, backward);
3967
3968
            // Ensure current selection is unaffected
3969
            if (backward) {
3970
                sel.setSingleRange(ranges[0], "backward");
3971
            } else {
3972
                sel.setRanges(ranges);
3973
            }
3974
3975
            return {
3976
                win: win,
3977
                rangeInfos: rangeInfos,
3978
                restored: false
3979
            };
3980
        }
3981
3982
        function restoreRanges(rangeInfos) {
3983
            var ranges = [];
3984
3985
            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
3986
            // normalization affecting previously restored ranges.
3987
            var rangeCount = rangeInfos.length;
3988
3989
            for (var i = rangeCount - 1; i >= 0; i--) {
3990
                ranges[i] = restoreRange(rangeInfos[i], true);
3991
            }
3992
3993
            return ranges;
3994
        }
3995
3996
        function restoreSelection(savedSelection, preserveDirection) {
3997
            if (!savedSelection.restored) {
3998
                var rangeInfos = savedSelection.rangeInfos;
3999
                var sel = api.getSelection(savedSelection.win);
4000
                var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
4001
4002
                if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
0 ignored issues
show
Best Practice introduced by
Comparing rangeCount to 1 using the == operator is not safe. Consider using === instead.
Loading history...
4003
                    sel.removeAllRanges();
4004
                    sel.addRange(ranges[0], true);
4005
                } else {
4006
                    sel.setRanges(ranges);
4007
                }
4008
4009
                savedSelection.restored = true;
4010
            }
4011
        }
4012
4013
        function removeMarkerElement(doc, markerId) {
4014
            var markerEl = gEBI(markerId, doc);
4015
            if (markerEl) {
4016
                markerEl.parentNode.removeChild(markerEl);
4017
            }
4018
        }
4019
4020
        function removeMarkers(savedSelection) {
4021
            var rangeInfos = savedSelection.rangeInfos;
4022
            for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
4023
                rangeInfo = rangeInfos[i];
4024
                if (rangeInfo.collapsed) {
4025
                    removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
4026
                } else {
4027
                    removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
4028
                    removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
4029
                }
4030
            }
4031
        }
4032
4033
        api.util.extend(api, {
4034
            saveRange: saveRange,
4035
            restoreRange: restoreRange,
4036
            saveRanges: saveRanges,
4037
            restoreRanges: restoreRanges,
4038
            saveSelection: saveSelection,
4039
            restoreSelection: restoreSelection,
4040
            removeMarkerElement: removeMarkerElement,
4041
            removeMarkers: removeMarkers
4042
        });
4043
    });
4044
    
4045
}, this);;/*
4046
	Base.js, version 1.1a
4047
	Copyright 2006-2010, Dean Edwards
4048
	License: http://www.opensource.org/licenses/mit-license.php
4049
*/
4050
4051
var Base = function() {
4052
	// dummy
4053
};
4054
4055
Base.extend = function(_instance, _static) { // subclass
4056
	var extend = Base.prototype.extend;
4057
	
4058
	// build the prototype
4059
	Base._prototyping = true;
4060
	var proto = new this;
4061
	extend.call(proto, _instance);
4062
  proto.base = function() {
4063
    // call this method from any other method to invoke that method's ancestor
4064
  };
4065
	delete Base._prototyping;
4066
	
4067
	// create the wrapper for the constructor function
4068
	//var constructor = proto.constructor.valueOf(); //-dean
4069
	var constructor = proto.constructor;
4070
	var klass = proto.constructor = function() {
4071
		if (!Base._prototyping) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !Base._prototyping is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4072
			if (this._constructing || this.constructor == klass) { // instantiation
4073
				this._constructing = true;
4074
				constructor.apply(this, arguments);
4075
				delete this._constructing;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
4076
			} else if (arguments[0] != null) { // casting
0 ignored issues
show
Best Practice introduced by
Comparing arguments.0 to null using the != operator is not safe. Consider using !== instead.
Loading history...
Complexity Best Practice introduced by
There is no return statement if arguments.0 != null is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4077
				return (arguments[0].extend || extend).call(arguments[0], proto);
4078
			}
4079
		}
4080
	};
4081
	
4082
	// build the class interface
4083
	klass.ancestor = this;
4084
	klass.extend = this.extend;
4085
	klass.forEach = this.forEach;
4086
	klass.implement = this.implement;
4087
	klass.prototype = proto;
4088
	klass.toString = this.toString;
4089
	klass.valueOf = function(type) {
4090
		//return (type == "object") ? klass : constructor; //-dean
4091
		return (type == "object") ? klass : constructor.valueOf();
4092
	};
4093
	extend.call(klass, _static);
4094
	// class initialisation
4095
	if (typeof klass.init == "function") klass.init();
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
4096
	return klass;
4097
};
4098
4099
Base.prototype = {	
4100
	extend: function(source, value) {
4101
		if (arguments.length > 1) { // extending with a name/value pair
4102
			var ancestor = this[source];
4103
			if (ancestor && (typeof value == "function") && // overriding a method?
4104
				// the valueOf() comparison is to avoid circular references
4105
				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
4106
				/\bbase\b/.test(value)) {
4107
				// get the underlying method
4108
				var method = value.valueOf();
4109
				// override
4110
				value = function() {
4111
					var previous = this.base || Base.prototype.base;
4112
					this.base = ancestor;
4113
					var returnValue = method.apply(this, arguments);
4114
					this.base = previous;
4115
					return returnValue;
4116
				};
4117
				// point to the underlying method
4118
				value.valueOf = function(type) {
4119
					return (type == "object") ? value : method;
4120
				};
4121
				value.toString = Base.toString;
4122
			}
4123
			this[source] = value;
4124
		} else if (source) { // extending with an object literal
4125
			var extend = Base.prototype.extend;
4126
			// if this object has a customised extend method then use it
4127
			if (!Base._prototyping && typeof this != "function") {
4128
				extend = this.extend || extend;
4129
			}
4130
			var proto = {toSource: null};
4131
			// do the "toString" and other methods manually
4132
			var hidden = ["constructor", "toString", "valueOf"];
4133
			// if we are prototyping then include the constructor
4134
			var i = Base._prototyping ? 0 : 1;
4135
			while (key = hidden[i++]) {
4136
				if (source[key] != proto[key]) {
4137
					extend.call(this, key, source[key]);
4138
4139
				}
4140
			}
4141
			// copy each of the source object's properties to this object
4142
			for (var key in source) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4143
				if (!proto[key]) extend.call(this, key, source[key]);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
4144
			}
4145
		}
4146
		return this;
4147
	}
4148
};
4149
4150
// initialise
4151
Base = Base.extend({
4152
	constructor: function() {
4153
		this.extend(arguments[0]);
4154
	}
4155
}, {
4156
	ancestor: Object,
4157
	version: "1.1",
4158
	
4159
	forEach: function(object, block, context) {
4160
		for (var key in object) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4161
			if (this.prototype[key] === undefined) {
4162
				block.call(context, object[key], key, object);
4163
			}
4164
		}
4165
	},
4166
		
4167
	implement: function() {
4168
		for (var i = 0; i < arguments.length; i++) {
4169
			if (typeof arguments[i] == "function") {
4170
				// if it's a function, call it
4171
				arguments[i](this.prototype);
4172
			} else {
4173
				// add the interface using the extend method
4174
				this.prototype.extend(arguments[i]);
4175
			}
4176
		}
4177
		return this;
4178
	},
4179
	
4180
	toString: function() {
4181
		return String(this.valueOf());
4182
	}
4183
});;/**
4184
 * Detect browser support for specific features
4185
 */
4186
wysihtml5.browser = (function() {
4187
  var userAgent   = navigator.userAgent,
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
4188
      testElement = document.createElement("div"),
4189
      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
4190
      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1,
4191
      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1,
4192
      isChrome    = userAgent.indexOf("Chrome/")      !== -1,
4193
      isOpera     = userAgent.indexOf("Opera/")       !== -1;
4194
4195
  function iosVersion(userAgent) {
4196
    return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
4197
  }
4198
4199
  function androidVersion(userAgent) {
4200
    return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
4201
  }
4202
4203
  function isIE(version, equation) {
4204
    var rv = -1,
4205
        re;
4206
4207
    if (navigator.appName == 'Microsoft Internet Explorer') {
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
4208
      re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
4209
    } else if (navigator.appName == 'Netscape') {
4210
      re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
4211
    }
4212
4213
    if (re && re.exec(navigator.userAgent) != null) {
0 ignored issues
show
Best Practice introduced by
Comparing re.exec(navigator.userAgent) to null using the != operator is not safe. Consider using !== instead.
Loading history...
4214
      rv = parseFloat(RegExp.$1);
4215
    }
4216
4217
    if (rv === -1) { return false; }
4218
    if (!version) { return true; }
4219
    if (!equation) { return version === rv; }
4220
    if (equation === "<") { return version < rv; }
4221
    if (equation === ">") { return version > rv; }
4222
    if (equation === "<=") { return version <= rv; }
4223
    if (equation === ">=") { return version >= rv; }
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if equation === ">=" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
4224
  }
4225
4226
  return {
4227
    // Static variable needed, publicly accessible, to be able override it in unit tests
4228
    USER_AGENT: userAgent,
4229
4230
    /**
4231
     * Exclude browsers that are not capable of displaying and handling
4232
     * contentEditable as desired:
4233
     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
4234
     *    - IE < 8 create invalid markup and crash randomly from time to time
4235
     *
4236
     * @return {Boolean}
4237
     */
4238
    supported: function() {
4239
      var userAgent                   = this.USER_AGENT.toLowerCase(),
4240
          // Essential for making html elements editable
4241
          hasContentEditableSupport   = "contentEditable" in testElement,
4242
          // Following methods are needed in order to interact with the contentEditable area
4243
          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
4244
          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
4245
          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
4246
          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
4247
          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
4248
      return hasContentEditableSupport
4249
        && hasEditingApiSupport
4250
        && hasQuerySelectorSupport
4251
        && !isIncompatibleMobileBrowser;
4252
    },
4253
4254
    isTouchDevice: function() {
4255
      return this.supportsEvent("touchmove");
4256
    },
4257
4258
    isIos: function() {
4259
      return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
4260
    },
4261
4262
    isAndroid: function() {
4263
      return this.USER_AGENT.indexOf("Android") !== -1;
4264
    },
4265
4266
    /**
4267
     * Whether the browser supports sandboxed iframes
4268
     * Currently only IE 6+ offers such feature <iframe security="restricted">
4269
     *
4270
     * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
4271
     * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
4272
     *
4273
     * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
4274
     */
4275
    supportsSandboxedIframes: function() {
4276
      return isIE();
4277
    },
4278
4279
    /**
4280
     * IE6+7 throw a mixed content warning when the src of an iframe
4281
     * is empty/unset or about:blank
4282
     * window.querySelector is implemented as of IE8
4283
     */
4284
    throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
4285
      return !("querySelector" in document);
4286
    },
4287
4288
    /**
4289
     * Whether the caret is correctly displayed in contentEditable elements
4290
     * Firefox sometimes shows a huge caret in the beginning after focusing
4291
     */
4292
    displaysCaretInEmptyContentEditableCorrectly: function() {
4293
      return isIE();
4294
    },
4295
4296
    /**
4297
     * Opera and IE are the only browsers who offer the css value
4298
     * in the original unit, thx to the currentStyle object
4299
     * All other browsers provide the computed style in px via window.getComputedStyle
4300
     */
4301
    hasCurrentStyleProperty: function() {
4302
      return "currentStyle" in testElement;
4303
    },
4304
4305
    /**
4306
     * Firefox on OSX navigates through history when hitting CMD + Arrow right/left
4307
     */
4308
    hasHistoryIssue: function() {
4309
      return isGecko && navigator.platform.substr(0, 3) === "Mac";
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
4310
    },
4311
4312
    /**
4313
     * Whether the browser inserts a <br> when pressing enter in a contentEditable element
4314
     */
4315
    insertsLineBreaksOnReturn: function() {
4316
      return isGecko;
4317
    },
4318
4319
    supportsPlaceholderAttributeOn: function(element) {
4320
      return "placeholder" in element;
4321
    },
4322
4323
    supportsEvent: function(eventName) {
4324
      return "on" + eventName in testElement || (function() {
4325
        testElement.setAttribute("on" + eventName, "return;");
4326
        return typeof(testElement["on" + eventName]) === "function";
4327
      })();
4328
    },
4329
4330
    /**
4331
     * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
4332
     */
4333
    supportsEventsInIframeCorrectly: function() {
4334
      return !isOpera;
4335
    },
4336
4337
    /**
4338
     * Everything below IE9 doesn't know how to treat HTML5 tags
4339
     *
4340
     * @param {Object} context The document object on which to check HTML5 support
4341
     *
4342
     * @example
4343
     *    wysihtml5.browser.supportsHTML5Tags(document);
4344
     */
4345
    supportsHTML5Tags: function(context) {
4346
      var element = context.createElement("div"),
4347
          html5   = "<article>foo</article>";
4348
      element.innerHTML = html5;
4349
      return element.innerHTML.toLowerCase() === html5;
4350
    },
4351
4352
    /**
4353
     * Checks whether a document supports a certain queryCommand
4354
     * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
4355
     * in oder to report correct results
4356
     *
4357
     * @param {Object} doc Document object on which to check for a query command
4358
     * @param {String} command The query command to check for
4359
     * @return {Boolean}
4360
     *
4361
     * @example
4362
     *    wysihtml5.browser.supportsCommand(document, "bold");
4363
     */
4364
    supportsCommand: (function() {
4365
      // Following commands are supported but contain bugs in some browsers
4366
      var buggyCommands = {
4367
        // formatBlock fails with some tags (eg. <blockquote>)
4368
        "formatBlock":          isIE(10, "<="),
4369
         // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
4370
         // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
4371
         // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
4372
        "insertUnorderedList":  isIE(),
4373
        "insertOrderedList":    isIE()
4374
      };
4375
4376
      // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
4377
      var supported = {
4378
        "insertHTML": isGecko
4379
      };
4380
4381
      return function(doc, command) {
4382
        var isBuggy = buggyCommands[command];
4383
        if (!isBuggy) {
4384
          // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
4385
          try {
4386
            return doc.queryCommandSupported(command);
4387
          } catch(e1) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
4388
4389
          try {
4390
            return doc.queryCommandEnabled(command);
4391
          } catch(e2) {
4392
            return !!supported[command];
4393
          }
4394
        }
4395
        return false;
4396
      };
4397
    })(),
4398
4399
    /**
4400
     * IE: URLs starting with:
4401
     *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
4402
     *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
4403
     * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
4404
     * space bar when the caret is directly after such an url.
4405
     * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
4406
     * (related blog post on msdn
4407
     * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
4408
     */
4409
    doesAutoLinkingInContentEditable: function() {
4410
      return isIE();
4411
    },
4412
4413
    /**
4414
     * As stated above, IE auto links urls typed into contentEditable elements
4415
     * Since IE9 it's possible to prevent this behavior
4416
     */
4417
    canDisableAutoLinking: function() {
4418
      return this.supportsCommand(document, "AutoUrlDetect");
4419
    },
4420
4421
    /**
4422
     * IE leaves an empty paragraph in the contentEditable element after clearing it
4423
     * Chrome/Safari sometimes an empty <div>
4424
     */
4425
    clearsContentEditableCorrectly: function() {
4426
      return isGecko || isOpera || isWebKit;
4427
    },
4428
4429
    /**
4430
     * IE gives wrong results for getAttribute
4431
     */
4432
    supportsGetAttributeCorrectly: function() {
4433
      var td = document.createElement("td");
4434
      return td.getAttribute("rowspan") != "1";
4435
    },
4436
4437
    /**
4438
     * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
4439
     * Chrome and Safari both don't support this
4440
     */
4441
    canSelectImagesInContentEditable: function() {
4442
      return isGecko || isIE() || isOpera;
4443
    },
4444
4445
    /**
4446
     * All browsers except Safari and Chrome automatically scroll the range/caret position into view
4447
     */
4448
    autoScrollsToCaret: function() {
4449
      return !isWebKit;
4450
    },
4451
4452
    /**
4453
     * Check whether the browser automatically closes tags that don't need to be opened
4454
     */
4455
    autoClosesUnclosedTags: function() {
4456
      var clonedTestElement = testElement.cloneNode(false),
4457
          returnValue,
4458
          innerHTML;
4459
4460
      clonedTestElement.innerHTML = "<p><div></div>";
4461
      innerHTML                   = clonedTestElement.innerHTML.toLowerCase();
4462
      returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
4463
4464
      // Cache result by overwriting current function
4465
      this.autoClosesUnclosedTags = function() { return returnValue; };
4466
4467
      return returnValue;
4468
    },
4469
4470
    /**
4471
     * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
4472
     */
4473
    supportsNativeGetElementsByClassName: function() {
4474
      return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
4475
    },
4476
4477
    /**
4478
     * As of now (19.04.2011) only supported by Firefox 4 and Chrome
4479
     * See https://developer.mozilla.org/en/DOM/Selection/modify
4480
     */
4481
    supportsSelectionModify: function() {
4482
      return "getSelection" in window && "modify" in window.getSelection();
4483
    },
4484
4485
    /**
4486
     * Opera needs a white space after a <br> in order to position the caret correctly
4487
     */
4488
    needsSpaceAfterLineBreak: function() {
4489
      return isOpera;
4490
    },
4491
4492
    /**
4493
     * Whether the browser supports the speech api on the given element
4494
     * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
4495
     *
4496
     * @example
4497
     *    var input = document.createElement("input");
4498
     *    if (wysihtml5.browser.supportsSpeechApiOn(input)) {
4499
     *      // ...
4500
     *    }
4501
     */
4502
    supportsSpeechApiOn: function(input) {
4503
      var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
4504
      return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
4505
    },
4506
4507
    /**
4508
     * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
4509
     * See https://connect.microsoft.com/ie/feedback/details/650112
4510
     * or try the POC http://tifftiff.de/ie9_crash/
4511
     */
4512
    crashesWhenDefineProperty: function(property) {
4513
      return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
4514
    },
4515
4516
    /**
4517
     * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
4518
     */
4519
    doesAsyncFocus: function() {
4520
      return isIE();
4521
    },
4522
4523
    /**
4524
     * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
4525
     */
4526
    hasProblemsSettingCaretAfterImg: function() {
4527
      return isIE();
4528
    },
4529
4530
    hasUndoInContextMenu: function() {
4531
      return isGecko || isChrome || isOpera;
4532
    },
4533
4534
    /**
4535
     * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
4536
     * is used (regardless if rangy or native)
4537
     * This especially happens when the caret is positioned right after a <br> because then
4538
     * insertNode() will insert the node right before the <br>
4539
     */
4540
    hasInsertNodeIssue: function() {
4541
      return isOpera;
4542
    },
4543
4544
    /**
4545
     * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
4546
     */
4547
    hasIframeFocusIssue: function() {
4548
      return isIE();
4549
    },
4550
4551
    /**
4552
     * Chrome + Safari create invalid nested markup after paste
4553
     *
4554
     *  <p>
4555
     *    foo
4556
     *    <p>bar</p> <!-- BOO! -->
4557
     *  </p>
4558
     */
4559
    createsNestedInvalidMarkupAfterPaste: function() {
4560
      return isWebKit;
4561
    },
4562
4563
    supportsMutationEvents: function() {
4564
        return ("MutationEvent" in window);
4565
    }
4566
  };
4567
})();
4568
;wysihtml5.lang.array = function(arr) {
4569
  return {
4570
    /**
4571
     * Check whether a given object exists in an array
4572
     *
4573
     * @example
4574
     *    wysihtml5.lang.array([1, 2]).contains(1);
4575
     *    // => true
4576
     *
4577
     * Can be used to match array with array. If intersection is found true is returned
4578
     */
4579
    contains: function(needle) {
4580
      if (Array.isArray(needle)) {
4581
        for (var i = needle.length; i--;) {
4582
          if (wysihtml5.lang.array(arr).indexOf(needle[i]) !== -1) {
4583
            return true;
4584
          }
4585
        }
4586
        return false;
4587
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
4588
        return wysihtml5.lang.array(arr).indexOf(needle) !== -1;
4589
      }
4590
    },
4591
4592
    /**
4593
     * Check whether a given object exists in an array and return index
4594
     * If no elelemt found returns -1
4595
     *
4596
     * @example
4597
     *    wysihtml5.lang.array([1, 2]).indexOf(2);
4598
     *    // => 1
4599
     */
4600
    indexOf: function(needle) {
4601
        if (arr.indexOf) {
4602
          return arr.indexOf(needle);
4603
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
4604
          for (var i=0, length=arr.length; i<length; i++) {
4605
            if (arr[i] === needle) { return i; }
4606
          }
4607
          return -1;
4608
        }
4609
    },
4610
4611
    /**
4612
     * Substract one array from another
4613
     *
4614
     * @example
4615
     *    wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
4616
     *    // => [1, 2]
4617
     */
4618
    without: function(arrayToSubstract) {
4619
      arrayToSubstract = wysihtml5.lang.array(arrayToSubstract);
4620
      var newArr  = [],
4621
          i       = 0,
4622
          length  = arr.length;
4623
      for (; i<length; i++) {
4624
        if (!arrayToSubstract.contains(arr[i])) {
4625
          newArr.push(arr[i]);
4626
        }
4627
      }
4628
      return newArr;
4629
    },
4630
4631
    /**
4632
     * Return a clean native array
4633
     *
4634
     * Following will convert a Live NodeList to a proper Array
4635
     * @example
4636
     *    var childNodes = wysihtml5.lang.array(document.body.childNodes).get();
4637
     */
4638
    get: function() {
4639
      var i        = 0,
4640
          length   = arr.length,
4641
          newArray = [];
4642
      for (; i<length; i++) {
4643
        newArray.push(arr[i]);
4644
      }
4645
      return newArray;
4646
    },
4647
4648
    /**
4649
     * Creates a new array with the results of calling a provided function on every element in this array.
4650
     * optionally this can be provided as second argument
4651
     *
4652
     * @example
4653
     *    var childNodes = wysihtml5.lang.array([1,2,3,4]).map(function (value, index, array) {
4654
            return value * 2;
4655
     *    });
4656
     *    // => [2,4,6,8]
4657
     */
4658
    map: function(callback, thisArg) {
4659
      if (Array.prototype.map) {
4660
        return arr.map(callback, thisArg);
4661
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
4662
        var len = arr.length >>> 0,
4663
            A = new Array(len),
4664
            i = 0;
4665
        for (; i < len; i++) {
4666
           A[i] = callback.call(thisArg, arr[i], i, arr);
4667
        }
4668
        return A;
4669
      }
4670
    },
4671
4672
    /* ReturnS new array without duplicate entries
4673
     *
4674
     * @example
4675
     *    var uniq = wysihtml5.lang.array([1,2,3,2,1,4]).unique();
4676
     *    // => [1,2,3,4]
4677
     */
4678
    unique: function() {
4679
      var vals = [],
4680
          max = arr.length,
4681
          idx = 0;
4682
4683
      while (idx < max) {
4684
        if (!wysihtml5.lang.array(vals).contains(arr[idx])) {
4685
          vals.push(arr[idx]);
4686
        }
4687
        idx++;
4688
      }
4689
      return vals;
4690
    }
4691
4692
  };
4693
};
4694
;wysihtml5.lang.Dispatcher = Base.extend(
4695
  /** @scope wysihtml5.lang.Dialog.prototype */ {
4696
  on: function(eventName, handler) {
4697
    this.events = this.events || {};
4698
    this.events[eventName] = this.events[eventName] || [];
4699
    this.events[eventName].push(handler);
4700
    return this;
4701
  },
4702
4703
  off: function(eventName, handler) {
4704
    this.events = this.events || {};
4705
    var i = 0,
4706
        handlers,
4707
        newHandlers;
4708
    if (eventName) {
4709
      handlers    = this.events[eventName] || [],
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
4710
      newHandlers = [];
4711
      for (; i<handlers.length; i++) {
4712
        if (handlers[i] !== handler && handler) {
4713
          newHandlers.push(handlers[i]);
4714
        }
4715
      }
4716
      this.events[eventName] = newHandlers;
4717
    } else {
4718
      // Clean up all events
4719
      this.events = {};
4720
    }
4721
    return this;
4722
  },
4723
4724
  fire: function(eventName, payload) {
4725
    this.events = this.events || {};
4726
    var handlers = this.events[eventName] || [],
4727
        i        = 0;
4728
    for (; i<handlers.length; i++) {
4729
      handlers[i].call(this, payload);
4730
    }
4731
    return this;
4732
  },
4733
4734
  // deprecated, use .on()
4735
  observe: function() {
4736
    return this.on.apply(this, arguments);
4737
  },
4738
4739
  // deprecated, use .off()
4740
  stopObserving: function() {
4741
    return this.off.apply(this, arguments);
4742
  }
4743
});
4744
;wysihtml5.lang.object = function(obj) {
4745
  return {
4746
    /**
4747
     * @example
4748
     *    wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
4749
     *    // => { foo: 1, bar: 2, baz: 3 }
4750
     */
4751
    merge: function(otherObj) {
4752
      for (var i in otherObj) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4753
        obj[i] = otherObj[i];
4754
      }
4755
      return this;
4756
    },
4757
4758
    get: function() {
4759
      return obj;
4760
    },
4761
4762
    /**
4763
     * @example
4764
     *    wysihtml5.lang.object({ foo: 1 }).clone();
4765
     *    // => { foo: 1 }
4766
     */
4767
    clone: function() {
4768
      var newObj = {},
4769
          i;
4770
      for (i in obj) {
4771
        newObj[i] = obj[i];
4772
      }
4773
      return newObj;
4774
    },
4775
4776
    /**
4777
     * @example
4778
     *    wysihtml5.lang.object([]).isArray();
4779
     *    // => true
4780
     */
4781
    isArray: function() {
4782
      return Object.prototype.toString.call(obj) === "[object Array]";
4783
    }
4784
  };
4785
};
4786
;(function() {
4787
  var WHITE_SPACE_START = /^\s+/,
4788
      WHITE_SPACE_END   = /\s+$/,
4789
      ENTITY_REG_EXP    = /[&<>"]/g,
4790
      ENTITY_MAP = {
4791
        '&': '&amp;',
4792
        '<': '&lt;',
4793
        '>': '&gt;',
4794
        '"': "&quot;"
4795
      };
4796
  wysihtml5.lang.string = function(str) {
4797
    str = String(str);
4798
    return {
4799
      /**
4800
       * @example
4801
       *    wysihtml5.lang.string("   foo   ").trim();
4802
       *    // => "foo"
4803
       */
4804
      trim: function() {
4805
        return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
4806
      },
4807
4808
      /**
4809
       * @example
4810
       *    wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
4811
       *    // => "Hello Christopher"
4812
       */
4813
      interpolate: function(vars) {
4814
        for (var i in vars) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
4815
          str = this.replace("#{" + i + "}").by(vars[i]);
4816
        }
4817
        return str;
4818
      },
4819
4820
      /**
4821
       * @example
4822
       *    wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans");
4823
       *    // => "Hello Hans"
4824
       */
4825
      replace: function(search) {
4826
        return {
4827
          by: function(replace) {
4828
            return str.split(search).join(replace);
4829
          }
4830
        };
4831
      },
4832
4833
      /**
4834
       * @example
4835
       *    wysihtml5.lang.string("hello<br>").escapeHTML();
4836
       *    // => "hello&lt;br&gt;"
4837
       */
4838
      escapeHTML: function() {
4839
        return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
4840
      }
4841
    };
4842
  };
4843
})();
4844
;/**
4845
 * Find urls in descendant text nodes of an element and auto-links them
4846
 * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
4847
 *
4848
 * @param {Element} element Container element in which to search for urls
4849
 *
4850
 * @example
4851
 *    <div id="text-container">Please click here: www.google.com</div>
4852
 *    <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
4853
 */
4854
(function(wysihtml5) {
4855
  var /**
4856
       * Don't auto-link urls that are contained in the following elements:
4857
       */
4858
      IGNORE_URLS_IN        = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
4859
      /**
4860
       * revision 1:
4861
       *    /(\S+\.{1}[^\s\,\.\!]+)/g
4862
       *
4863
       * revision 2:
4864
       *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
4865
       *
4866
       * put this in the beginning if you don't wan't to match within a word
4867
       *    (^|[\>\(\{\[\s\>])
4868
       */
4869
      URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
4870
      TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
4871
      MAX_DISPLAY_LENGTH    = 100,
4872
      BRACKETS              = { ")": "(", "]": "[", "}": "{" };
4873
4874
  function autoLink(element, ignoreInClasses) {
4875
    if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
4876
      return element;
4877
    }
4878
4879
    if (element === element.ownerDocument.documentElement) {
4880
      element = element.ownerDocument.body;
4881
    }
4882
4883
    return _parseNode(element, ignoreInClasses);
4884
  }
4885
4886
  /**
4887
   * This is basically a rebuild of
4888
   * the rails auto_link_urls text helper
4889
   */
4890
  function _convertUrlsToLinks(str) {
4891
    return str.replace(URL_REG_EXP, function(match, url) {
4892
      var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
4893
          opening     = BRACKETS[punctuation];
4894
      url = url.replace(TRAILING_CHAR_REG_EXP, "");
4895
4896
      if (url.split(opening).length > url.split(punctuation).length) {
4897
        url = url + punctuation;
4898
        punctuation = "";
4899
      }
4900
      var realUrl    = url,
4901
          displayUrl = url;
4902
      if (url.length > MAX_DISPLAY_LENGTH) {
4903
        displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
4904
      }
4905
      // Add http prefix if necessary
4906
      if (realUrl.substr(0, 4) === "www.") {
4907
        realUrl = "http://" + realUrl;
4908
      }
4909
4910
      return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
4911
    });
4912
  }
4913
4914
  /**
4915
   * Creates or (if already cached) returns a temp element
4916
   * for the given document object
4917
   */
4918
  function _getTempElement(context) {
4919
    var tempElement = context._wysihtml5_tempElement;
4920
    if (!tempElement) {
4921
      tempElement = context._wysihtml5_tempElement = context.createElement("div");
4922
    }
4923
    return tempElement;
4924
  }
4925
4926
  /**
4927
   * Replaces the original text nodes with the newly auto-linked dom tree
4928
   */
4929
  function _wrapMatchesInNode(textNode) {
4930
    var parentNode  = textNode.parentNode,
4931
        nodeValue   = wysihtml5.lang.string(textNode.data).escapeHTML(),
4932
        tempElement = _getTempElement(parentNode.ownerDocument);
4933
4934
    // We need to insert an empty/temporary <span /> to fix IE quirks
4935
    // Elsewise IE would strip white space in the beginning
4936
    tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
4937
    tempElement.removeChild(tempElement.firstChild);
4938
4939
    while (tempElement.firstChild) {
4940
      // inserts tempElement.firstChild before textNode
4941
      parentNode.insertBefore(tempElement.firstChild, textNode);
4942
    }
4943
    parentNode.removeChild(textNode);
4944
  }
4945
4946
  function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
4947
    var nodeName;
4948
    while (node.parentNode) {
4949
      node = node.parentNode;
4950
      nodeName = node.nodeName;
4951
      if (node.className && wysihtml5.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
4952
        return true;
4953
      }
4954
      if (IGNORE_URLS_IN.contains(nodeName)) {
4955
        return true;
4956
      } else if (nodeName === "body") {
4957
        return false;
4958
      }
4959
    }
4960
    return false;
4961
  }
4962
4963
  function _parseNode(element, ignoreInClasses) {
4964
    if (IGNORE_URLS_IN.contains(element.nodeName)) {
4965
      return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
4966
    }
4967
4968
    if (element.className && wysihtml5.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
4969
      return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
4970
    }
4971
4972
    if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) {
4973
      _wrapMatchesInNode(element);
4974
      return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
4975
    }
4976
4977
    var childNodes        = wysihtml5.lang.array(element.childNodes).get(),
4978
        childNodesLength  = childNodes.length,
4979
        i                 = 0;
4980
4981
    for (; i<childNodesLength; i++) {
4982
      _parseNode(childNodes[i], ignoreInClasses);
4983
    }
4984
4985
    return element;
4986
  }
4987
4988
  wysihtml5.dom.autoLink = autoLink;
4989
4990
  // Reveal url reg exp to the outside
4991
  wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
4992
})(wysihtml5);
4993
;(function(wysihtml5) {
4994
  var api = wysihtml5.dom;
4995
4996
  api.addClass = function(element, className) {
4997
    var classList = element.classList;
4998
    if (classList) {
4999
      return classList.add(className);
5000
    }
5001
    if (api.hasClass(element, className)) {
5002
      return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
5003
    }
5004
    element.className += " " + className;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
5005
  };
5006
5007
  api.removeClass = function(element, className) {
5008
    var classList = element.classList;
5009
    if (classList) {
5010
      return classList.remove(className);
5011
    }
5012
5013
    element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ");
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
5014
  };
5015
5016
  api.hasClass = function(element, className) {
5017
    var classList = element.classList;
5018
    if (classList) {
5019
      return classList.contains(className);
5020
    }
5021
5022
    var elementClassName = element.className;
5023
    return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
5024
  };
5025
})(wysihtml5);
5026
;wysihtml5.dom.contains = (function() {
5027
  var documentElement = document.documentElement;
5028
  if (documentElement.contains) {
5029
    return function(container, element) {
5030
      if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5031
        element = element.parentNode;
5032
      }
5033
      return container !== element && container.contains(element);
5034
    };
5035
  } else if (documentElement.compareDocumentPosition) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if documentElement.compareDocumentPosition is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
5036
    return function(container, element) {
5037
      // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
5038
      return !!(container.compareDocumentPosition(element) & 16);
5039
    };
5040
  }
5041
})();
5042
;/**
5043
 * Converts an HTML fragment/element into a unordered/ordered list
5044
 *
5045
 * @param {Element} element The element which should be turned into a list
5046
 * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
5047
 * @return {Element} The created list
5048
 *
5049
 * @example
5050
 *    <!-- Assume the following dom: -->
5051
 *    <span id="pseudo-list">
5052
 *      eminem<br>
5053
 *      dr. dre
5054
 *      <div>50 Cent</div>
5055
 *    </span>
5056
 *
5057
 *    <script>
5058
 *      wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul");
5059
 *    </script>
5060
 *
5061
 *    <!-- Will result in: -->
5062
 *    <ul>
5063
 *      <li>eminem</li>
5064
 *      <li>dr. dre</li>
5065
 *      <li>50 Cent</li>
5066
 *    </ul>
5067
 */
5068
wysihtml5.dom.convertToList = (function() {
5069
  function _createListItem(doc, list) {
5070
    var listItem = doc.createElement("li");
5071
    list.appendChild(listItem);
5072
    return listItem;
5073
  }
5074
5075
  function _createList(doc, type) {
5076
    return doc.createElement(type);
5077
  }
5078
5079
  function convertToList(element, listType, uneditableClass) {
5080
    if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
5081
      // Already a list
5082
      return element;
5083
    }
5084
5085
    var doc               = element.ownerDocument,
5086
        list              = _createList(doc, listType),
5087
        lineBreaks        = element.querySelectorAll("br"),
5088
        lineBreaksLength  = lineBreaks.length,
5089
        childNodes,
5090
        childNodesLength,
5091
        childNode,
5092
        lineBreak,
5093
        parentNode,
5094
        isBlockElement,
5095
        isLineBreak,
5096
        currentListItem,
5097
        i;
5098
5099
    // First find <br> at the end of inline elements and move them behind them
5100
    for (i=0; i<lineBreaksLength; i++) {
5101
      lineBreak = lineBreaks[i];
5102
      while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
5103
        if (wysihtml5.dom.getStyle("display").from(parentNode) === "block") {
5104
          parentNode.removeChild(lineBreak);
5105
          break;
5106
        }
5107
        wysihtml5.dom.insert(lineBreak).after(lineBreak.parentNode);
5108
      }
5109
    }
5110
5111
    childNodes        = wysihtml5.lang.array(element.childNodes).get();
5112
    childNodesLength  = childNodes.length;
5113
5114
    for (i=0; i<childNodesLength; i++) {
5115
      currentListItem   = currentListItem || _createListItem(doc, list);
5116
      childNode         = childNodes[i];
5117
      isBlockElement    = wysihtml5.dom.getStyle("display").from(childNode) === "block";
5118
      isLineBreak       = childNode.nodeName === "BR";
5119
5120
      // consider uneditable as an inline element
5121
      if (isBlockElement && (!uneditableClass || !wysihtml5.dom.hasClass(childNode, uneditableClass))) {
5122
        // Append blockElement to current <li> if empty, otherwise create a new one
5123
        currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
5124
        currentListItem.appendChild(childNode);
5125
        currentListItem = null;
5126
        continue;
5127
      }
5128
5129
      if (isLineBreak) {
5130
        // Only create a new list item in the next iteration when the current one has already content
5131
        currentListItem = currentListItem.firstChild ? null : currentListItem;
5132
        continue;
5133
      }
5134
5135
      currentListItem.appendChild(childNode);
5136
    }
5137
5138
    if (childNodes.length === 0) {
5139
      _createListItem(doc, list);
5140
    }
5141
5142
    element.parentNode.replaceChild(list, element);
5143
    return list;
5144
  }
5145
5146
  return convertToList;
5147
})();
5148
;/**
5149
 * Copy a set of attributes from one element to another
5150
 *
5151
 * @param {Array} attributesToCopy List of attributes which should be copied
5152
 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
5153
 *    copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
5154
 *    with the element where to copy the attributes to (see example)
5155
 *
5156
 * @example
5157
 *    var textarea    = document.querySelector("textarea"),
5158
 *        div         = document.querySelector("div[contenteditable=true]"),
5159
 *        anotherDiv  = document.querySelector("div.preview");
5160
 *    wysihtml5.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
5161
 *
5162
 */
5163
wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5164
  return {
5165
    from: function(elementToCopyFrom) {
5166
      return {
5167
        to: function(elementToCopyTo) {
5168
          var attribute,
5169
              i         = 0,
5170
              length    = attributesToCopy.length;
5171
          for (; i<length; i++) {
5172
            attribute = attributesToCopy[i];
5173
            if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
5174
              elementToCopyTo[attribute] = elementToCopyFrom[attribute];
5175
            }
5176
          }
5177
          return { andTo: arguments.callee };
5178
        }
5179
      };
5180
    }
5181
  };
5182
};
5183
;/**
5184
 * Copy a set of styles from one element to another
5185
 * Please note that this only works properly across browsers when the element from which to copy the styles
5186
 * is in the dom
5187
 *
5188
 * Interesting article on how to copy styles
5189
 *
5190
 * @param {Array} stylesToCopy List of styles which should be copied
5191
 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
5192
 *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked
5193
 *    with the element where to copy the styles to (see example)
5194
 *
5195
 * @example
5196
 *    var textarea    = document.querySelector("textarea"),
5197
 *        div         = document.querySelector("div[contenteditable=true]"),
5198
 *        anotherDiv  = document.querySelector("div.preview");
5199
 *    wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
5200
 *
5201
 */
5202
(function(dom) {
5203
5204
  /**
5205
   * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
5206
   * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
5207
   * its computed css width will be 198px
5208
   *
5209
   * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
5210
   */
5211
  var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
5212
5213
  var shouldIgnoreBoxSizingBorderBox = function(element) {
5214
    if (hasBoxSizingBorderBox(element)) {
5215
       return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
5216
    }
5217
    return false;
5218
  };
5219
5220
  var hasBoxSizingBorderBox = function(element) {
5221
    var i       = 0,
5222
        length  = BOX_SIZING_PROPERTIES.length;
5223
    for (; i<length; i++) {
5224
      if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
5225
        return BOX_SIZING_PROPERTIES[i];
5226
      }
5227
    }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
5228
  };
5229
5230
  dom.copyStyles = function(stylesToCopy) {
5231
    return {
5232
      from: function(element) {
5233
        if (shouldIgnoreBoxSizingBorderBox(element)) {
5234
          stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
5235
        }
5236
5237
        var cssText = "",
5238
            length  = stylesToCopy.length,
5239
            i       = 0,
5240
            property;
5241
        for (; i<length; i++) {
5242
          property = stylesToCopy[i];
5243
          cssText += property + ":" + dom.getStyle(property).from(element) + ";";
5244
        }
5245
5246
        return {
5247
          to: function(element) {
5248
            dom.setStyles(cssText).on(element);
5249
            return { andTo: arguments.callee };
5250
          }
5251
        };
5252
      }
5253
    };
5254
  };
5255
})(wysihtml5.dom);
5256
;/**
5257
 * Event Delegation
5258
 *
5259
 * @example
5260
 *    wysihtml5.dom.delegate(document.body, "a", "click", function() {
5261
 *      // foo
5262
 *    });
5263
 */
5264
(function(wysihtml5) {
5265
5266
  wysihtml5.dom.delegate = function(container, selector, eventName, handler) {
5267
    return wysihtml5.dom.observe(container, eventName, function(event) {
5268
      var target    = event.target,
5269
          match     = wysihtml5.lang.array(container.querySelectorAll(selector));
5270
5271
      while (target && target !== container) {
5272
        if (match.contains(target)) {
5273
          handler.call(target, event);
5274
          break;
5275
        }
5276
        target = target.parentNode;
5277
      }
5278
    });
5279
  };
5280
5281
})(wysihtml5);
5282
;// TODO: Refactor dom tree traversing here
5283
(function(wysihtml5) {
5284
  wysihtml5.dom.domNode = function(node) {
5285
    var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
5286
5287
    var _isBlankText = function(node) {
5288
      return node.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/g).test(node.data);
5289
    };
5290
5291
    return {
5292
5293
      // var node = wysihtml5.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
5294
      prev: function(options) {
5295
        var prevNode = node.previousSibling,
5296
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5297
        
5298
        if (!prevNode) {
5299
          return null;
5300
        }
5301
5302
        if (
5303
          (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
5304
          (options && options.ignoreBlankTexts && _isBlankText(prevNode)) // Blank text nodes bypassed if set
5305
        ) {
5306
          return wysihtml5.dom.domNode(prevNode).prev(options);
5307
        }
5308
        
5309
        return prevNode;
5310
      },
5311
5312
      // var node = wysihtml5.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
5313
      next: function(options) {
5314
        var nextNode = node.nextSibling,
5315
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5316
        
5317
        if (!nextNode) {
5318
          return null;
5319
        }
5320
5321
        if (
5322
          (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
5323
          (options && options.ignoreBlankTexts && _isBlankText(nextNode)) // blank text nodes bypassed if set
5324
        ) {
5325
          return wysihtml5.dom.domNode(nextNode).next(options);
5326
        }
5327
        
5328
        return nextNode;
5329
      }
5330
5331
5332
5333
    };
5334
  };
5335
})(wysihtml5);;/**
5336
 * Returns the given html wrapped in a div element
5337
 *
5338
 * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
5339
 * when inserted via innerHTML
5340
 *
5341
 * @param {String} html The html which should be wrapped in a dom element
5342
 * @param {Obejct} [context] Document object of the context the html belongs to
5343
 *
5344
 * @example
5345
 *    wysihtml5.dom.getAsDom("<article>foo</article>");
5346
 */
5347
wysihtml5.dom.getAsDom = (function() {
5348
5349
  var _innerHTMLShiv = function(html, context) {
5350
    var tempElement = context.createElement("div");
5351
    tempElement.style.display = "none";
5352
    context.body.appendChild(tempElement);
5353
    // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
5354
    try { tempElement.innerHTML = html; } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
5355
    context.body.removeChild(tempElement);
5356
    return tempElement;
5357
  };
5358
5359
  /**
5360
   * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
5361
   */
5362
  var _ensureHTML5Compatibility = function(context) {
5363
    if (context._wysihtml5_supportsHTML5Tags) {
5364
      return;
5365
    }
5366
    for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
5367
      context.createElement(HTML5_ELEMENTS[i]);
5368
    }
5369
    context._wysihtml5_supportsHTML5Tags = true;
5370
  };
5371
5372
5373
  /**
5374
   * List of html5 tags
5375
   * taken from http://simon.html5.org/html5-elements
5376
   */
5377
  var HTML5_ELEMENTS = [
5378
    "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
5379
    "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
5380
    "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
5381
  ];
5382
5383
  return function(html, context) {
5384
    context = context || document;
5385
    var tempElement;
5386
    if (typeof(html) === "object" && html.nodeType) {
5387
      tempElement = context.createElement("div");
5388
      tempElement.appendChild(html);
5389
    } else if (wysihtml5.browser.supportsHTML5Tags(context)) {
5390
      tempElement = context.createElement("div");
5391
      tempElement.innerHTML = html;
5392
    } else {
5393
      _ensureHTML5Compatibility(context);
5394
      tempElement = _innerHTMLShiv(html, context);
5395
    }
5396
    return tempElement;
5397
  };
5398
})();
5399
;/**
5400
 * Walks the dom tree from the given node up until it finds a match
5401
 * Designed for optimal performance.
5402
 *
5403
 * @param {Element} node The from which to check the parent nodes
5404
 * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
5405
 * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
5406
 * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
5407
 * @example
5408
 *    var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] });
5409
 *    // ... or ...
5410
 *    var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" });
5411
 *    // ... or ...
5412
 *    var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g });
5413
 */
5414
wysihtml5.dom.getParentElement = (function() {
5415
5416
  function _isSameNodeName(nodeName, desiredNodeNames) {
5417
    if (!desiredNodeNames || !desiredNodeNames.length) {
5418
      return true;
5419
    }
5420
5421
    if (typeof(desiredNodeNames) === "string") {
5422
      return nodeName === desiredNodeNames;
5423
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
5424
      return wysihtml5.lang.array(desiredNodeNames).contains(nodeName);
5425
    }
5426
  }
5427
5428
  function _isElement(node) {
5429
    return node.nodeType === wysihtml5.ELEMENT_NODE;
5430
  }
5431
5432
  function _hasClassName(element, className, classRegExp) {
5433
    var classNames = (element.className || "").match(classRegExp) || [];
5434
    if (!className) {
5435
      return !!classNames.length;
5436
    }
5437
    return classNames[classNames.length - 1] === className;
5438
  }
5439
5440
  function _hasStyle(element, cssStyle, styleRegExp) {
5441
    var styles = (element.getAttribute('style') || "").match(styleRegExp) || [];
5442
    if (!cssStyle) {
5443
      return !!styles.length;
5444
    }
5445
    return styles[styles.length - 1] === cssStyle;
5446
  }
5447
5448
  return function(node, matchingSet, levels, container) {
5449
    var findByStyle = (matchingSet.cssStyle || matchingSet.styleRegExp),
5450
        findByClass = (matchingSet.className || matchingSet.classRegExp);
5451
5452
    levels = levels || 50; // Go max 50 nodes upwards from current node
5453
5454
    while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
5455
      if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
5456
          (!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
5457
          (!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
5458
      ) {
5459
        return node;
5460
      }
5461
      node = node.parentNode;
5462
    }
5463
    return null;
5464
  };
5465
})();
5466
;/**
5467
 * Get element's style for a specific css property
5468
 *
5469
 * @param {Element} element The element on which to retrieve the style
5470
 * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
5471
 *
5472
 * @example
5473
 *    wysihtml5.dom.getStyle("display").from(document.body);
5474
 *    // => "block"
5475
 */
5476
wysihtml5.dom.getStyle = (function() {
5477
  var stylePropertyMapping = {
5478
        "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
5479
      },
5480
      REG_EXP_CAMELIZE = /\-[a-z]/g;
5481
5482
  function camelize(str) {
5483
    return str.replace(REG_EXP_CAMELIZE, function(match) {
5484
      return match.charAt(1).toUpperCase();
5485
    });
5486
  }
5487
5488
  return function(property) {
5489
    return {
5490
      from: function(element) {
5491
        if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5492
          return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
5493
        }
5494
5495
        var doc               = element.ownerDocument,
5496
            camelizedProperty = stylePropertyMapping[property] || camelize(property),
5497
            style             = element.style,
5498
            currentStyle      = element.currentStyle,
5499
            styleValue        = style[camelizedProperty];
5500
        if (styleValue) {
5501
          return styleValue;
5502
        }
5503
5504
        // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
5505
        // window.getComputedStyle, since it returns css property values in their original unit:
5506
        // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
5507
        // gives you the original "50%".
5508
        // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
5509
        if (currentStyle) {
5510
          try {
5511
            return currentStyle[camelizedProperty];
5512
          } catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
5513
            //ie will occasionally fail for unknown reasons. swallowing exception
5514
          }
5515
        }
5516
5517
        var win                 = doc.defaultView || doc.parentWindow,
5518
            needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
5519
            originalOverflow,
5520
            returnValue;
5521
5522
        if (win.getComputedStyle) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if win.getComputedStyle is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
5523
          // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
5524
          // therfore we remove and restore the scrollbar and calculate the value in between
5525
          if (needsOverflowReset) {
5526
            originalOverflow = style.overflow;
5527
            style.overflow = "hidden";
5528
          }
5529
          returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
5530
          if (needsOverflowReset) {
5531
            style.overflow = originalOverflow || "";
5532
          }
5533
          return returnValue;
5534
        }
5535
      }
5536
    };
5537
  };
5538
})();
5539
;wysihtml5.dom.getTextNodes = function(node, ingoreEmpty){
5540
  var all = [];
5541
  for (node=node.firstChild;node;node=node.nextSibling){
5542
    if (node.nodeType == 3) {
5543
      if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
5544
        all.push(node);
5545
      }
5546
    } else {
5547
      all = all.concat(wysihtml5.dom.getTextNodes(node, ingoreEmpty));
5548
    }
5549
  }
5550
  return all;
5551
};;/**
5552
 * High performant way to check whether an element with a specific tag name is in the given document
5553
 * Optimized for being heavily executed
5554
 * Unleashes the power of live node lists
5555
 *
5556
 * @param {Object} doc The document object of the context where to check
5557
 * @param {String} tagName Upper cased tag name
5558
 * @example
5559
 *    wysihtml5.dom.hasElementWithTagName(document, "IMG");
5560
 */
5561
wysihtml5.dom.hasElementWithTagName = (function() {
5562
  var LIVE_CACHE          = {},
5563
      DOCUMENT_IDENTIFIER = 1;
5564
5565
  function _getDocumentIdentifier(doc) {
5566
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5567
  }
5568
5569
  return function(doc, tagName) {
5570
    var key         = _getDocumentIdentifier(doc) + ":" + tagName,
5571
        cacheEntry  = LIVE_CACHE[key];
5572
    if (!cacheEntry) {
5573
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
5574
    }
5575
5576
    return cacheEntry.length > 0;
5577
  };
5578
})();
5579
;/**
5580
 * High performant way to check whether an element with a specific class name is in the given document
5581
 * Optimized for being heavily executed
5582
 * Unleashes the power of live node lists
5583
 *
5584
 * @param {Object} doc The document object of the context where to check
5585
 * @param {String} tagName Upper cased tag name
5586
 * @example
5587
 *    wysihtml5.dom.hasElementWithClassName(document, "foobar");
5588
 */
5589
(function(wysihtml5) {
5590
  var LIVE_CACHE          = {},
5591
      DOCUMENT_IDENTIFIER = 1;
5592
5593
  function _getDocumentIdentifier(doc) {
5594
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5595
  }
5596
5597
  wysihtml5.dom.hasElementWithClassName = function(doc, className) {
5598
    // getElementsByClassName is not supported by IE<9
5599
    // but is sometimes mocked via library code (which then doesn't return live node lists)
5600
    if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) {
5601
      return !!doc.querySelector("." + className);
5602
    }
5603
5604
    var key         = _getDocumentIdentifier(doc) + ":" + className,
5605
        cacheEntry  = LIVE_CACHE[key];
5606
    if (!cacheEntry) {
5607
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
5608
    }
5609
5610
    return cacheEntry.length > 0;
5611
  };
5612
})(wysihtml5);
5613
;wysihtml5.dom.insert = function(elementToInsert) {
5614
  return {
5615
    after: function(element) {
5616
      element.parentNode.insertBefore(elementToInsert, element.nextSibling);
5617
    },
5618
5619
    before: function(element) {
5620
      element.parentNode.insertBefore(elementToInsert, element);
5621
    },
5622
5623
    into: function(element) {
5624
      element.appendChild(elementToInsert);
5625
    }
5626
  };
5627
};
5628
;wysihtml5.dom.insertCSS = function(rules) {
5629
  rules = rules.join("\n");
5630
5631
  return {
5632
    into: function(doc) {
5633
      var styleElement = doc.createElement("style");
5634
      styleElement.type = "text/css";
5635
5636
      if (styleElement.styleSheet) {
5637
        styleElement.styleSheet.cssText = rules;
5638
      } else {
5639
        styleElement.appendChild(doc.createTextNode(rules));
5640
      }
5641
5642
      var link = doc.querySelector("head link");
5643
      if (link) {
5644
        link.parentNode.insertBefore(styleElement, link);
5645
        return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
5646
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
5647
        var head = doc.querySelector("head");
5648
        if (head) {
5649
          head.appendChild(styleElement);
5650
        }
5651
      }
5652
    }
5653
  };
5654
};
5655
;// TODO: Refactor dom tree traversing here
5656
(function(wysihtml5) {
5657
  wysihtml5.dom.lineBreaks = function(node) {
5658
5659
    function _isLineBreak(n) {
5660
      return n.nodeName === "BR";
5661
    }
5662
5663
    /**
5664
     * Checks whether the elment causes a visual line break
5665
     * (<br> or block elements)
5666
     */
5667
    function _isLineBreakOrBlockElement(element) {
5668
      if (_isLineBreak(element)) {
5669
        return true;
5670
      }
5671
5672
      if (wysihtml5.dom.getStyle("display").from(element) === "block") {
5673
        return true;
5674
      }
5675
5676
      return false;
5677
    }
5678
5679
    return {
5680
5681
      /* wysihtml5.dom.lineBreaks(element).add();
5682
       *
5683
       * Adds line breaks before and after the given node if the previous and next siblings
5684
       * aren't already causing a visual line break (block element or <br>)
5685
       */
5686
      add: function(options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
5687
        var doc             = node.ownerDocument,
5688
          nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5689
          previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5690
5691
        if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
5692
          wysihtml5.dom.insert(doc.createElement("br")).after(node);
5693
        }
5694
        if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
5695
          wysihtml5.dom.insert(doc.createElement("br")).before(node);
5696
        }
5697
      },
5698
5699
      /* wysihtml5.dom.lineBreaks(element).remove();
5700
       *
5701
       * Removes line breaks before and after the given node
5702
       */
5703
      remove: function(options) {
0 ignored issues
show
Unused Code introduced by
The parameter options is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
5704
        var nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5705
            previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5706
5707
        if (nextSibling && _isLineBreak(nextSibling)) {
5708
          nextSibling.parentNode.removeChild(nextSibling);
5709
        }
5710
        if (previousSibling && _isLineBreak(previousSibling)) {
5711
          previousSibling.parentNode.removeChild(previousSibling);
5712
        }
5713
      }
5714
    };
5715
  };
5716
})(wysihtml5);;/**
5717
 * Method to set dom events
5718
 *
5719
 * @example
5720
 *    wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
5721
 */
5722
wysihtml5.dom.observe = function(element, eventNames, handler) {
5723
  eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
5724
5725
  var handlerWrapper,
5726
      eventName,
5727
      i       = 0,
5728
      length  = eventNames.length;
5729
5730
  for (; i<length; i++) {
5731
    eventName = eventNames[i];
5732
    if (element.addEventListener) {
5733
      element.addEventListener(eventName, handler, false);
5734
    } else {
5735
      handlerWrapper = function(event) {
5736
        if (!("target" in event)) {
5737
          event.target = event.srcElement;
5738
        }
5739
        event.preventDefault = event.preventDefault || function() {
5740
          this.returnValue = false;
5741
        };
5742
        event.stopPropagation = event.stopPropagation || function() {
5743
          this.cancelBubble = true;
5744
        };
5745
        handler.call(element, event);
5746
      };
5747
      element.attachEvent("on" + eventName, handlerWrapper);
5748
    }
5749
  }
5750
5751
  return {
5752
    stop: function() {
5753
      var eventName,
5754
          i       = 0,
5755
          length  = eventNames.length;
5756
      for (; i<length; i++) {
5757
        eventName = eventNames[i];
5758
        if (element.removeEventListener) {
5759
          element.removeEventListener(eventName, handler, false);
5760
        } else {
5761
          element.detachEvent("on" + eventName, handlerWrapper);
0 ignored issues
show
Bug introduced by
The variable handlerWrapper seems to not be initialized for all possible execution paths. Are you sure detachEvent handles undefined variables?
Loading history...
5762
        }
5763
      }
5764
    }
5765
  };
5766
};
5767
;/**
5768
 * HTML Sanitizer
5769
 * Rewrites the HTML based on given rules
5770
 *
5771
 * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
5772
 * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
5773
 *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
5774
 *    desired substitution.
5775
 * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
5776
 *
5777
 * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
5778
 *
5779
 * @example
5780
 *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
5781
 *    wysihtml5.dom.parse(userHTML, {
5782
 *      tags {
5783
 *        p:      "div",      // Rename p tags to div tags
5784
 *        font:   "span"      // Rename font tags to span tags
5785
 *        div:    true,       // Keep them, also possible (same result when passing: "div" or true)
5786
 *        script: undefined   // Remove script elements
5787
 *      }
5788
 *    });
5789
 *    // => <div><div><span>foo bar</span></div></div>
5790
 *
5791
 *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
5792
 *    wysihtml5.dom.parse(userHTML);
5793
 *    // => '<span><span><span><span>I'm a table!</span></span></span></span>'
5794
 *
5795
 *    var userHTML = '<div>foobar<br>foobar</div>';
5796
 *    wysihtml5.dom.parse(userHTML, {
5797
 *      tags: {
5798
 *        div: undefined,
5799
 *        br:  true
5800
 *      }
5801
 *    });
5802
 *    // => ''
5803
 *
5804
 *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
5805
 *    wysihtml5.dom.parse(userHTML, {
5806
 *      classes: {
5807
 *        red:    1,
5808
 *        green:  1
5809
 *      },
5810
 *      tags: {
5811
 *        div: {
5812
 *          rename_tag:     "p"
5813
 *        }
5814
 *      }
5815
 *    });
5816
 *    // => '<p class="red">foo</p><p>bar</p>'
5817
 */
5818
5819
wysihtml5.dom.parse = (function() {
5820
5821
  /**
5822
   * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
5823
   * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
5824
   * node isn't closed
5825
   *
5826
   * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
5827
   */
5828
  var NODE_TYPE_MAPPING = {
5829
        "1": _handleElement,
5830
        "3": _handleText,
5831
        "8": _handleComment
5832
      },
5833
      // Rename unknown tags to this
5834
      DEFAULT_NODE_NAME   = "span",
5835
      WHITE_SPACE_REG_EXP = /\s+/,
5836
      defaultRules        = { tags: {}, classes: {} },
5837
      currentRules        = {},
5838
      uneditableClass     = false;
5839
5840
  /**
5841
   * Iterates over all childs of the element, recreates them, appends them into a document fragment
5842
   * which later replaces the entire body content
5843
   */
5844
   function parse(elementOrHtml, config) {
5845
    wysihtml5.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
5846
5847
    var context       = config.context || elementOrHtml.ownerDocument || document,
5848
        fragment      = context.createDocumentFragment(),
5849
        isString      = typeof(elementOrHtml) === "string",
5850
        clearInternals = false,
5851
        element,
5852
        newNode,
5853
        firstChild;
5854
5855
    if (config.clearInternals === true) {
5856
      clearInternals = true;
5857
    }
5858
5859
    if (config.uneditableClass) {
5860
      uneditableClass = config.uneditableClass;
5861
    }
5862
5863
    if (isString) {
5864
      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
5865
    } else {
5866
      element = elementOrHtml;
5867
    }
5868
5869
    while (element.firstChild) {
5870
      firstChild = element.firstChild;
5871
      newNode = _convert(firstChild, config.cleanUp, clearInternals);
5872
      if (newNode) {
5873
        fragment.appendChild(newNode);
5874
      }
5875
      if (firstChild !== newNode) {
5876
        element.removeChild(firstChild);
5877
      }
5878
    }
5879
5880
    // Clear element contents
5881
    element.innerHTML = "";
5882
5883
    // Insert new DOM tree
5884
    element.appendChild(fragment);
5885
5886
    return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
5887
  }
5888
5889
  function _convert(oldNode, cleanUp, clearInternals) {
5890
    var oldNodeType     = oldNode.nodeType,
5891
        oldChilds       = oldNode.childNodes,
5892
        oldChildsLength = oldChilds.length,
5893
        method          = NODE_TYPE_MAPPING[oldNodeType],
5894
        i               = 0,
5895
        fragment,
5896
        newNode,
5897
        newChild;
5898
5899
    // Passes directly elemets with uneditable class
5900
    if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
5901
        return oldNode;
5902
    }
5903
5904
    newNode = method && method(oldNode, clearInternals);
5905
5906
    // Remove or unwrap node in case of return value null or false
5907
    if (!newNode) {
5908
        if (newNode === false) {
5909
            // false defines that tag should be removed but contents should remain (unwrap)
5910
            fragment = oldNode.ownerDocument.createDocumentFragment();
5911
5912
            for (i = oldChildsLength; i--;) {
5913
              if (oldChilds[i]) {
5914
                newChild = _convert(oldChilds[i], cleanUp, clearInternals);
5915
                if (newChild) {
5916
                  if (oldChilds[i] === newChild) {
5917
                    i--;
5918
                  }
5919
                  fragment.insertBefore(newChild, fragment.firstChild);
5920
                }
5921
              }
5922
            }
5923
5924
            // TODO: try to minimize surplus spaces
5925
            if (wysihtml5.lang.array([
5926
                "div", "pre", "p",
5927
                "table", "td", "th",
5928
                "ul", "ol", "li",
5929
                "dd", "dl",
5930
                "footer", "header", "section",
5931
                "h1", "h2", "h3", "h4", "h5", "h6"
5932
            ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
5933
                // add space at first when unwraping non-textflow elements
5934
                if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
5935
                  fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
5936
                }
5937
            }
5938
5939
            if (fragment.normalize) {
5940
              fragment.normalize();
5941
            }
5942
            return fragment;
5943
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
5944
          // Remove
5945
          return null;
5946
        }
5947
    }
5948
5949
    // Converts all childnodes
5950
    for (i=0; i<oldChildsLength; i++) {
5951
      if (oldChilds[i]) {
5952
        newChild = _convert(oldChilds[i], cleanUp, clearInternals);
5953
        if (newChild) {
5954
          if (oldChilds[i] === newChild) {
5955
            i--;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
5956
          }
5957
          newNode.appendChild(newChild);
5958
        }
5959
      }
5960
    }
5961
5962
    // Cleanup senseless <span> elements
5963
    if (cleanUp &&
5964
        newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
5965
        (!newNode.childNodes.length ||
5966
         ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml5-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
5967
         !newNode.attributes.length)
5968
        ) {
5969
      fragment = newNode.ownerDocument.createDocumentFragment();
5970
      while (newNode.firstChild) {
5971
        fragment.appendChild(newNode.firstChild);
5972
      }
5973
      if (fragment.normalize) {
5974
        fragment.normalize();
5975
      }
5976
      return fragment;
5977
    }
5978
5979
    if (newNode.normalize) {
5980
      newNode.normalize();
5981
    }
5982
    return newNode;
5983
  }
5984
5985
  function _handleElement(oldNode, clearInternals) {
5986
    var rule,
5987
        newNode,
5988
        tagRules    = currentRules.tags,
5989
        nodeName    = oldNode.nodeName.toLowerCase(),
5990
        scopeName   = oldNode.scopeName;
5991
5992
    /**
5993
     * We already parsed that element
5994
     * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
5995
     */
5996
    if (oldNode._wysihtml5) {
5997
      return null;
5998
    }
5999
    oldNode._wysihtml5 = 1;
6000
6001
    if (oldNode.className === "wysihtml5-temp") {
6002
      return null;
6003
    }
6004
6005
    /**
6006
     * IE is the only browser who doesn't include the namespace in the
6007
     * nodeName, that's why we have to prepend it by ourselves
6008
     * scopeName is a proprietary IE feature
6009
     * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
6010
     */
6011
    if (scopeName && scopeName != "HTML") {
6012
      nodeName = scopeName + ":" + nodeName;
6013
    }
6014
    /**
6015
     * Repair node
6016
     * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
6017
     * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout
6018
     */
6019
    if ("outerHTML" in oldNode) {
6020
      if (!wysihtml5.browser.autoClosesUnclosedTags() &&
6021
          oldNode.nodeName === "P" &&
6022
          oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
6023
        nodeName = "div";
6024
      }
6025
    }
6026
6027
    if (nodeName in tagRules) {
6028
      rule = tagRules[nodeName];
6029
      if (!rule || rule.remove) {
6030
        return null;
6031
      } else if (rule.unwrap) {
6032
        return false;
6033
      }
6034
      rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
6035
    } else if (oldNode.firstChild) {
6036
      rule = { rename_tag: DEFAULT_NODE_NAME };
6037
    } else {
6038
      // Remove empty unknown elements
6039
      return null;
6040
    }
6041
6042
    newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
6043
    _handleAttributes(oldNode, newNode, rule, clearInternals);
6044
    _handleStyles(oldNode, newNode, rule);
6045
    // tests if type condition is met or node should be removed/unwrapped
6046
    if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
6047
      return (rule.remove_action && rule.remove_action == "unwrap") ? false : null;
6048
    }
6049
6050
    oldNode = null;
0 ignored issues
show
Unused Code introduced by
The assignment to oldNode seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
6051
6052
    if (newNode.normalize) { newNode.normalize(); }
6053
    return newNode;
6054
  }
6055
6056
  function _testTypes(oldNode, rules, types, clearInternals) {
6057
    var definition, type;
6058
6059
    // do not interfere with placeholder span or pasting caret position is not maintained
6060
    if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml5-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
6061
      return true;
6062
    }
6063
6064
    for (type in types) {
6065
      if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
6066
        definition = rules.type_definitions[type];
6067
        if (_testType(oldNode, definition)) {
6068
          return true;
6069
        }
6070
      }
6071
    }
6072
    return false;
6073
  }
6074
6075
  function array_contains(a, obj) {
0 ignored issues
show
introduced by
The function array_contains does not seem to be used and can be removed.
Loading history...
6076
      var i = a.length;
6077
      while (i--) {
6078
         if (a[i] === obj) {
6079
             return true;
6080
         }
6081
      }
6082
      return false;
6083
  }
6084
6085
  function _testType(oldNode, definition) {
6086
6087
    var nodeClasses = oldNode.getAttribute("class"),
6088
        nodeStyles =  oldNode.getAttribute("style"),
6089
        classesLength, s, s_corrected, a, attr, currentClass, styleProp;
0 ignored issues
show
Unused Code introduced by
The variable s_corrected seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable currentClass seems to be never used. Consider removing it.
Loading history...
6090
6091
    // test for methods
6092
    if (definition.methods) {
6093
      for (var m in definition.methods) {
6094
        if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
6095
6096
          if (typeCeckMethods[m](oldNode)) {
6097
            return true;
6098
          }
6099
        }
6100
      }
6101
    }
6102
6103
    // test for classes, if one found return true
6104
    if (nodeClasses && definition.classes) {
6105
      nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
6106
      classesLength = nodeClasses.length;
6107
      for (var i = 0; i < classesLength; i++) {
6108
        if (definition.classes[nodeClasses[i]]) {
6109
          return true;
6110
        }
6111
      }
6112
    }
6113
6114
    // test for styles, if one found return true
6115
    if (nodeStyles && definition.styles) {
6116
6117
      nodeStyles = nodeStyles.split(';');
6118
      for (s in definition.styles) {
6119
        if (definition.styles.hasOwnProperty(s)) {
6120
          for (var sp = nodeStyles.length; sp--;) {
6121
            styleProp = nodeStyles[sp].split(':');
6122
6123
            if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
6124
              if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml5.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
6125
                return true;
6126
              }
6127
            }
6128
          }
6129
        }
6130
      }
6131
    }
6132
6133
    // test for attributes in general against regex match
6134
    if (definition.attrs) {
6135
        for (a in definition.attrs) {
6136
            if (definition.attrs.hasOwnProperty(a)) {
6137
                attr = _getAttribute(oldNode, a);
6138
                if (typeof(attr) === "string") {
6139
                    if (attr.search(definition.attrs[a]) > -1) {
6140
                        return true;
6141
                    }
6142
                }
6143
            }
6144
        }
6145
    }
6146
    return false;
6147
  }
6148
6149
  function _handleStyles(oldNode, newNode, rule) {
6150
    var s;
6151
    if(rule && rule.keep_styles) {
6152
      for (s in rule.keep_styles) {
6153
        if (rule.keep_styles.hasOwnProperty(s)) {
6154
          if (s == "float") {
6155
            // IE compability
6156
            if (oldNode.style.styleFloat) {
6157
              newNode.style.styleFloat = oldNode.style.styleFloat;
6158
            }
6159
            if (oldNode.style.cssFloat) {
6160
              newNode.style.cssFloat = oldNode.style.cssFloat;
6161
            }
6162
           } else if (oldNode.style[s]) {
6163
             newNode.style[s] = oldNode.style[s];
6164
           }
6165
        }
6166
      }
6167
    }
6168
  }
6169
6170
  // TODO: refactor. Too long to read
6171
  function _handleAttributes(oldNode, newNode, rule, clearInternals) {
6172
    var attributes          = {},                         // fresh new set of attributes to set on newNode
6173
        setClass            = rule.set_class,             // classes to set
6174
        addClass            = rule.add_class,             // add classes based on existing attributes
6175
        addStyle            = rule.add_style,             // add styles based on existing attributes
6176
        setAttributes       = rule.set_attributes,        // attributes to set on the current node
6177
        checkAttributes     = rule.check_attributes,      // check/convert values of attributes
6178
        allowedClasses      = currentRules.classes,
6179
        i                   = 0,
6180
        classes             = [],
6181
        styles              = [],
6182
        newClasses          = [],
6183
        oldClasses          = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable oldClasses seems to be never used. Consider removing it.
Loading history...
6184
        classesLength,
6185
        newClassesLength,
0 ignored issues
show
Unused Code introduced by
The variable newClassesLength seems to be never used. Consider removing it.
Loading history...
6186
        currentClass,
6187
        newClass,
6188
        attributeName,
6189
        newAttributeValue,
6190
        method,
6191
        oldAttribute;
6192
6193
    if (setAttributes) {
6194
      attributes = wysihtml5.lang.object(setAttributes).clone();
6195
    }
6196
6197
    if (checkAttributes) {
6198
      for (attributeName in checkAttributes) {
6199
        method = attributeCheckMethods[checkAttributes[attributeName]];
6200
        if (!method) {
6201
          continue;
6202
        }
6203
        oldAttribute = _getAttribute(oldNode, attributeName);
6204
        if (oldAttribute || (attributeName === "alt" && oldNode.nodeName == "IMG")) {
6205
          newAttributeValue = method(oldAttribute);
6206
          if (typeof(newAttributeValue) === "string") {
6207
            attributes[attributeName] = newAttributeValue;
6208
          }
6209
        }
6210
      }
6211
    }
6212
6213
    if (setClass) {
6214
      classes.push(setClass);
6215
    }
6216
6217
    if (addClass) {
6218
      for (attributeName in addClass) {
6219
        method = addClassMethods[addClass[attributeName]];
6220
        if (!method) {
6221
          continue;
6222
        }
6223
        newClass = method(_getAttribute(oldNode, attributeName));
6224
        if (typeof(newClass) === "string") {
6225
          classes.push(newClass);
6226
        }
6227
      }
6228
    }
6229
6230
    if (addStyle) {
6231
      for (attributeName in addStyle) {
6232
        method = addStyleMethods[addStyle[attributeName]];
6233
        if (!method) {
6234
          continue;
6235
        }
6236
6237
        newStyle = method(_getAttribute(oldNode, attributeName));
0 ignored issues
show
Bug introduced by
The variable newStyle seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.newStyle.
Loading history...
6238
        if (typeof(newStyle) === "string") {
6239
          styles.push(newStyle);
6240
        }
6241
      }
6242
    }
6243
6244
6245
    if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
6246
      attributes["class"] = oldNode.getAttribute("class");
6247
    } else {
6248
      // make sure that wysihtml5 temp class doesn't get stripped out
6249
      if (!clearInternals) {
6250
        allowedClasses["_wysihtml5-temp-placeholder"] = 1;
6251
        allowedClasses["_rangySelectionBoundary"] = 1;
6252
        allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
6253
      }
6254
6255
      // add old classes last
6256
      oldClasses = oldNode.getAttribute("class");
6257
      if (oldClasses) {
6258
        classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
6259
      }
6260
      classesLength = classes.length;
6261
      for (; i<classesLength; i++) {
6262
        currentClass = classes[i];
6263
        if (allowedClasses[currentClass]) {
6264
          newClasses.push(currentClass);
6265
        }
6266
      }
6267
6268
      if (newClasses.length) {
6269
        attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
6270
      }
6271
    }
6272
6273
    // remove table selection class if present
6274
    if (attributes["class"] && clearInternals) {
6275
      attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
6276
      if ((/^\s*$/g).test(attributes["class"])) {
6277
        delete attributes["class"];
6278
      }
6279
    }
6280
6281
    if (styles.length) {
6282
      attributes["style"] = wysihtml5.lang.array(styles).unique().join(" ");
6283
    }
6284
6285
    // set attributes on newNode
6286
    for (attributeName in attributes) {
6287
      // Setting attributes can cause a js error in IE under certain circumstances
6288
      // eg. on a <img> under https when it's new attribute value is non-https
6289
      // TODO: Investigate this further and check for smarter handling
6290
      try {
6291
        newNode.setAttribute(attributeName, attributes[attributeName]);
6292
      } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
6293
    }
6294
6295
    // IE8 sometimes loses the width/height attributes when those are set before the "src"
6296
    // so we make sure to set them again
6297
    if (attributes.src) {
6298
      if (typeof(attributes.width) !== "undefined") {
6299
        newNode.setAttribute("width", attributes.width);
6300
      }
6301
      if (typeof(attributes.height) !== "undefined") {
6302
        newNode.setAttribute("height", attributes.height);
6303
      }
6304
    }
6305
  }
6306
6307
  /**
6308
   * IE gives wrong results for hasAttribute/getAttribute, for example:
6309
   *    var td = document.createElement("td");
6310
   *    td.getAttribute("rowspan"); // => "1" in IE
6311
   *
6312
   * Therefore we have to check the element's outerHTML for the attribute
6313
   */
6314
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
6315
  function _getAttribute(node, attributeName) {
6316
    attributeName = attributeName.toLowerCase();
6317
    var nodeName = node.nodeName;
6318
    if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
6319
      // Get 'src' attribute value via object property since this will always contain the
6320
      // full absolute url (http://...)
6321
      // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
6322
      // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
6323
      return node.src;
6324
    } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
6325
      // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
6326
      var outerHTML      = node.outerHTML.toLowerCase(),
6327
          // TODO: This might not work for attributes without value: <input disabled>
6328
          hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
6329
6330
      return hasAttribute ? node.getAttribute(attributeName) : null;
6331
    } else{
6332
      return node.getAttribute(attributeName);
6333
    }
6334
  }
6335
6336
  /**
6337
   * Check whether the given node is a proper loaded image
6338
   * FIXME: Returns undefined when unknown (Chrome, Safari)
6339
   */
6340
  function _isLoadedImage(node) {
6341
    try {
6342
      return node.complete && !node.mozMatchesSelector(":-moz-broken");
6343
    } catch(e) {
6344
      if (node.complete && node.readyState === "complete") {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if node.complete && node.readyState === "complete" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
6345
        return true;
6346
      }
6347
    }
6348
  }
6349
6350
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
6351
  function _handleText(oldNode) {
6352
    var nextSibling = oldNode.nextSibling;
6353
    if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
6354
      // Concatenate text nodes
6355
      nextSibling.data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(INVISIBLE_SPACE_REG_EXP, "");
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
6356
    } else {
6357
      // \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
6358
      var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
6359
      return oldNode.ownerDocument.createTextNode(data);
6360
    }
6361
  }
6362
6363
  function _handleComment(oldNode) {
6364
    if (currentRules.comments) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if currentRules.comments is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
6365
      return oldNode.ownerDocument.createComment(oldNode.nodeValue);
6366
    }
6367
  }
6368
6369
  // ------------ attribute checks ------------ \\
6370
  var attributeCheckMethods = {
6371
    url: (function() {
6372
      var REG_EXP = /^https?:\/\//i;
6373
      return function(attributeValue) {
6374
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6375
          return null;
6376
        }
6377
        return attributeValue.replace(REG_EXP, function(match) {
6378
          return match.toLowerCase();
6379
        });
6380
      };
6381
    })(),
6382
6383
    src: (function() {
6384
      var REG_EXP = /^(\/|https?:\/\/)/i;
6385
      return function(attributeValue) {
6386
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6387
          return null;
6388
        }
6389
        return attributeValue.replace(REG_EXP, function(match) {
6390
          return match.toLowerCase();
6391
        });
6392
      };
6393
    })(),
6394
6395
    href: (function() {
6396
      var REG_EXP = /^(#|\/|https?:\/\/|mailto:)/i;
6397
      return function(attributeValue) {
6398
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6399
          return null;
6400
        }
6401
        return attributeValue.replace(REG_EXP, function(match) {
6402
          return match.toLowerCase();
6403
        });
6404
      };
6405
    })(),
6406
6407
    alt: (function() {
6408
      var REG_EXP = /[^ a-z0-9_\-]/gi;
6409
      return function(attributeValue) {
6410
        if (!attributeValue) {
6411
          return "";
6412
        }
6413
        return attributeValue.replace(REG_EXP, "");
6414
      };
6415
    })(),
6416
6417
    numbers: (function() {
6418
      var REG_EXP = /\D/g;
6419
      return function(attributeValue) {
6420
        attributeValue = (attributeValue || "").replace(REG_EXP, "");
6421
        return attributeValue || null;
6422
      };
6423
    })(),
6424
6425
    any: (function() {
6426
      return function(attributeValue) {
6427
        return attributeValue;
6428
      };
6429
    })()
6430
  };
6431
6432
  // ------------ style converter (converts an html attribute to a style) ------------ \\
6433
  var addStyleMethods = {
6434
    align_text: (function() {
6435
      var mapping = {
6436
        left:     "text-align: left;",
6437
        right:    "text-align: right;",
6438
        center:   "text-align: center;"
6439
      };
6440
      return function(attributeValue) {
6441
        return mapping[String(attributeValue).toLowerCase()];
6442
      };
6443
    })(),
6444
  };
6445
6446
  // ------------ class converter (converts an html attribute to a class name) ------------ \\
6447
  var addClassMethods = {
6448
    align_img: (function() {
6449
      var mapping = {
6450
        left:   "wysiwyg-float-left",
6451
        right:  "wysiwyg-float-right"
6452
      };
6453
      return function(attributeValue) {
6454
        return mapping[String(attributeValue).toLowerCase()];
6455
      };
6456
    })(),
6457
6458
    align_text: (function() {
6459
      var mapping = {
6460
        left:     "wysiwyg-text-align-left",
6461
        right:    "wysiwyg-text-align-right",
6462
        center:   "wysiwyg-text-align-center",
6463
        justify:  "wysiwyg-text-align-justify"
6464
      };
6465
      return function(attributeValue) {
6466
        return mapping[String(attributeValue).toLowerCase()];
6467
      };
6468
    })(),
6469
6470
    clear_br: (function() {
6471
      var mapping = {
6472
        left:   "wysiwyg-clear-left",
6473
        right:  "wysiwyg-clear-right",
6474
        both:   "wysiwyg-clear-both",
6475
        all:    "wysiwyg-clear-both"
6476
      };
6477
      return function(attributeValue) {
6478
        return mapping[String(attributeValue).toLowerCase()];
6479
      };
6480
    })(),
6481
6482
    size_font: (function() {
6483
      var mapping = {
6484
        "1": "wysiwyg-font-size-xx-small",
6485
        "2": "wysiwyg-font-size-small",
6486
        "3": "wysiwyg-font-size-medium",
6487
        "4": "wysiwyg-font-size-large",
6488
        "5": "wysiwyg-font-size-x-large",
6489
        "6": "wysiwyg-font-size-xx-large",
6490
        "7": "wysiwyg-font-size-xx-large",
6491
        "-": "wysiwyg-font-size-smaller",
6492
        "+": "wysiwyg-font-size-larger"
6493
      };
6494
      return function(attributeValue) {
6495
        return mapping[String(attributeValue).charAt(0)];
6496
      };
6497
    })()
6498
  };
6499
6500
  // checks if element is possibly visible
6501
  var typeCeckMethods = {
6502
    has_visible_contet: (function() {
6503
      var txt,
6504
          isVisible = false,
0 ignored issues
show
Unused Code introduced by
The variable isVisible seems to be never used. Consider removing it.
Loading history...
6505
          visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
6506
                             'style', 'table', 'iframe', 'object', 'embed', 'audio',
6507
                             'svg', 'input', 'button', 'select','textarea', 'canvas'];
6508
6509
      return function(el) {
6510
6511
        // has visible innertext. so is visible
6512
        txt = (el.innerText || el.textContent).replace(/\s/g, '');
6513
        if (txt && txt.length > 0) {
6514
          return true;
6515
        }
6516
6517
        // matches list of visible dimensioned elements
6518
        for (var i = visibleElements.length; i--;) {
6519
          if (el.querySelector(visibleElements[i])) {
6520
            return true;
6521
          }
6522
        }
6523
6524
        // try to measure dimesions in last resort. (can find only of elements in dom)
6525
        if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
6526
          return true;
6527
        }
6528
6529
        return false;
6530
      };
6531
    })()
6532
  };
6533
6534
  return parse;
6535
})();
6536
;/**
6537
 * Checks for empty text node childs and removes them
6538
 *
6539
 * @param {Element} node The element in which to cleanup
6540
 * @example
6541
 *    wysihtml5.dom.removeEmptyTextNodes(element);
6542
 */
6543
wysihtml5.dom.removeEmptyTextNodes = function(node) {
6544
  var childNode,
6545
      childNodes        = wysihtml5.lang.array(node.childNodes).get(),
6546
      childNodesLength  = childNodes.length,
6547
      i                 = 0;
6548
  for (; i<childNodesLength; i++) {
6549
    childNode = childNodes[i];
6550
    if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
6551
      childNode.parentNode.removeChild(childNode);
6552
    }
6553
  }
6554
};
6555
;/**
6556
 * Renames an element (eg. a <div> to a <p>) and keeps its childs
6557
 *
6558
 * @param {Element} element The list element which should be renamed
6559
 * @param {Element} newNodeName The desired tag name
6560
 *
6561
 * @example
6562
 *    <!-- Assume the following dom: -->
6563
 *    <ul id="list">
6564
 *      <li>eminem</li>
6565
 *      <li>dr. dre</li>
6566
 *      <li>50 Cent</li>
6567
 *    </ul>
6568
 *
6569
 *    <script>
6570
 *      wysihtml5.dom.renameElement(document.getElementById("list"), "ol");
6571
 *    </script>
6572
 *
6573
 *    <!-- Will result in: -->
6574
 *    <ol>
6575
 *      <li>eminem</li>
6576
 *      <li>dr. dre</li>
6577
 *      <li>50 Cent</li>
6578
 *    </ol>
6579
 */
6580
wysihtml5.dom.renameElement = function(element, newNodeName) {
6581
  var newElement = element.ownerDocument.createElement(newNodeName),
6582
      firstChild;
6583
  while (firstChild = element.firstChild) {
6584
    newElement.appendChild(firstChild);
6585
  }
6586
  wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
6587
  element.parentNode.replaceChild(newElement, element);
6588
  return newElement;
6589
};
6590
;/**
6591
 * Takes an element, removes it and replaces it with it's childs
6592
 *
6593
 * @param {Object} node The node which to replace with it's child nodes
6594
 * @example
6595
 *    <div id="foo">
6596
 *      <span>hello</span>
6597
 *    </div>
6598
 *    <script>
6599
 *      // Remove #foo and replace with it's children
6600
 *      wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo"));
6601
 *    </script>
6602
 */
6603
wysihtml5.dom.replaceWithChildNodes = function(node) {
6604
  if (!node.parentNode) {
6605
    return;
6606
  }
6607
6608
  if (!node.firstChild) {
6609
    node.parentNode.removeChild(node);
6610
    return;
6611
  }
6612
6613
  var fragment = node.ownerDocument.createDocumentFragment();
6614
  while (node.firstChild) {
6615
    fragment.appendChild(node.firstChild);
6616
  }
6617
  node.parentNode.replaceChild(fragment, node);
6618
  node = fragment = null;
0 ignored issues
show
Unused Code introduced by
The assignment to variable node seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The assignment to fragment seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
6619
};
6620
;/**
6621
 * Unwraps an unordered/ordered list
6622
 *
6623
 * @param {Element} element The list element which should be unwrapped
6624
 *
6625
 * @example
6626
 *    <!-- Assume the following dom: -->
6627
 *    <ul id="list">
6628
 *      <li>eminem</li>
6629
 *      <li>dr. dre</li>
6630
 *      <li>50 Cent</li>
6631
 *    </ul>
6632
 *
6633
 *    <script>
6634
 *      wysihtml5.dom.resolveList(document.getElementById("list"));
6635
 *    </script>
6636
 *
6637
 *    <!-- Will result in: -->
6638
 *    eminem<br>
6639
 *    dr. dre<br>
6640
 *    50 Cent<br>
6641
 */
6642
(function(dom) {
6643
  function _isBlockElement(node) {
6644
    return dom.getStyle("display").from(node) === "block";
6645
  }
6646
6647
  function _isLineBreak(node) {
6648
    return node.nodeName === "BR";
6649
  }
6650
6651
  function _appendLineBreak(element) {
6652
    var lineBreak = element.ownerDocument.createElement("br");
6653
    element.appendChild(lineBreak);
6654
  }
6655
6656
  function resolveList(list, useLineBreaks) {
6657
    if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
6658
      return;
6659
    }
6660
6661
    var doc             = list.ownerDocument,
6662
        fragment        = doc.createDocumentFragment(),
6663
        previousSibling = wysihtml5.dom.domNode(list).prev({ignoreBlankTexts: true}),
6664
        firstChild,
6665
        lastChild,
6666
        isLastChild,
6667
        shouldAppendLineBreak,
6668
        paragraph,
6669
        listItem;
6670
6671
    if (useLineBreaks) {
6672
      // Insert line break if list is after a non-block element
6673
      if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) {
6674
        _appendLineBreak(fragment);
6675
      }
6676
6677
      while (listItem = (list.firstElementChild || list.firstChild)) {
6678
        lastChild = listItem.lastChild;
6679
        while (firstChild = listItem.firstChild) {
6680
          isLastChild           = firstChild === lastChild;
6681
          // This needs to be done before appending it to the fragment, as it otherwise will lose style information
6682
          shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
6683
          fragment.appendChild(firstChild);
6684
          if (shouldAppendLineBreak) {
6685
            _appendLineBreak(fragment);
6686
          }
6687
        }
6688
6689
        listItem.parentNode.removeChild(listItem);
6690
      }
6691
    } else {
6692
      while (listItem = (list.firstElementChild || list.firstChild)) {
6693
        if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
6694
          while (firstChild = listItem.firstChild) {
6695
            fragment.appendChild(firstChild);
6696
          }
6697
        } else {
6698
          paragraph = doc.createElement("p");
6699
          while (firstChild = listItem.firstChild) {
6700
            paragraph.appendChild(firstChild);
6701
          }
6702
          fragment.appendChild(paragraph);
6703
        }
6704
        listItem.parentNode.removeChild(listItem);
6705
      }
6706
    }
6707
6708
    list.parentNode.replaceChild(fragment, list);
6709
  }
6710
6711
  dom.resolveList = resolveList;
6712
})(wysihtml5.dom);
6713
;/**
6714
 * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
6715
 *
6716
 * Browser Compatibility:
6717
 *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
6718
 *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
6719
 *
6720
 * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
6721
 *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
6722
 *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
6723
 *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
6724
 *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
6725
 *      can do anything as if the sandbox attribute wasn't set
6726
 *
6727
 * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
6728
 * @param {Object} [config] Optional parameters
6729
 *
6730
 * @example
6731
 *    new wysihtml5.dom.Sandbox(function(sandbox) {
6732
 *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
6733
 *    });
6734
 */
6735
(function(wysihtml5) {
6736
  var /**
6737
       * Default configuration
6738
       */
6739
      doc                 = document,
6740
      /**
6741
       * Properties to unset/protect on the window object
6742
       */
6743
      windowProperties    = [
6744
        "parent", "top", "opener", "frameElement", "frames",
6745
        "localStorage", "globalStorage", "sessionStorage", "indexedDB"
6746
      ],
6747
      /**
6748
       * Properties on the window object which are set to an empty function
6749
       */
6750
      windowProperties2   = [
6751
        "open", "close", "openDialog", "showModalDialog",
6752
        "alert", "confirm", "prompt",
6753
        "openDatabase", "postMessage",
6754
        "XMLHttpRequest", "XDomainRequest"
6755
      ],
6756
      /**
6757
       * Properties to unset/protect on the document object
6758
       */
6759
      documentProperties  = [
6760
        "referrer",
6761
        "write", "open", "close"
6762
      ];
6763
6764
  wysihtml5.dom.Sandbox = Base.extend(
6765
    /** @scope wysihtml5.dom.Sandbox.prototype */ {
6766
6767
    constructor: function(readyCallback, config) {
6768
      this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
6769
      this.config   = wysihtml5.lang.object({}).merge(config).get();
6770
      this.editableArea   = this._createIframe();
6771
    },
6772
6773
    insertInto: function(element) {
6774
      if (typeof(element) === "string") {
6775
        element = doc.getElementById(element);
6776
      }
6777
6778
      element.appendChild(this.editableArea);
6779
    },
6780
6781
    getIframe: function() {
6782
      return this.editableArea;
6783
    },
6784
6785
    getWindow: function() {
6786
      this._readyError();
6787
    },
6788
6789
    getDocument: function() {
6790
      this._readyError();
6791
    },
6792
6793
    destroy: function() {
6794
      var iframe = this.getIframe();
6795
      iframe.parentNode.removeChild(iframe);
6796
    },
6797
6798
    _readyError: function() {
6799
      throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");
6800
    },
6801
6802
    /**
6803
     * Creates the sandbox iframe
6804
     *
6805
     * Some important notes:
6806
     *  - We can't use HTML5 sandbox for now:
6807
     *    setting it causes that the iframe's dom can't be accessed from the outside
6808
     *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
6809
     *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
6810
     *    In order to make this happen we need to set the "allow-scripts" flag.
6811
     *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
6812
     *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
6813
     *  - IE needs to have the security="restricted" attribute set before the iframe is
6814
     *    inserted into the dom tree
6815
     *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
6816
     *    though it supports it
6817
     *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
6818
     *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
6819
     *    on the onreadystatechange event
6820
     */
6821
    _createIframe: function() {
6822
      var that   = this,
6823
          iframe = doc.createElement("iframe");
6824
      iframe.className = "wysihtml5-sandbox";
6825
      wysihtml5.dom.setAttributes({
6826
        "security":           "restricted",
6827
        "allowtransparency":  "true",
6828
        "frameborder":        0,
6829
        "width":              0,
6830
        "height":             0,
6831
        "marginwidth":        0,
6832
        "marginheight":       0
6833
      }).on(iframe);
6834
6835
      // Setting the src like this prevents ssl warnings in IE6
6836
      if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
6837
        iframe.src = "javascript:'<html></html>'";
6838
      }
6839
6840
      iframe.onload = function() {
6841
        iframe.onreadystatechange = iframe.onload = null;
6842
        that._onLoadIframe(iframe);
6843
      };
6844
6845
      iframe.onreadystatechange = function() {
6846
        if (/loaded|complete/.test(iframe.readyState)) {
6847
          iframe.onreadystatechange = iframe.onload = null;
6848
          that._onLoadIframe(iframe);
6849
        }
6850
      };
6851
6852
      return iframe;
6853
    },
6854
6855
    /**
6856
     * Callback for when the iframe has finished loading
6857
     */
6858
    _onLoadIframe: function(iframe) {
6859
      // don't resume when the iframe got unloaded (eg. by removing it from the dom)
6860
      if (!wysihtml5.dom.contains(doc.documentElement, iframe)) {
6861
        return;
6862
      }
6863
6864
      var that           = this,
6865
          iframeWindow   = iframe.contentWindow,
6866
          iframeDocument = iframe.contentWindow.document,
6867
          charset        = doc.characterSet || doc.charset || "utf-8",
6868
          sandboxHtml    = this._getHtml({
6869
            charset:      charset,
6870
            stylesheets:  this.config.stylesheets
6871
          });
6872
6873
      // Create the basic dom tree including proper DOCTYPE and charset
6874
      iframeDocument.open("text/html", "replace");
6875
      iframeDocument.write(sandboxHtml);
6876
      iframeDocument.close();
6877
6878
      this.getWindow = function() { return iframe.contentWindow; };
6879
      this.getDocument = function() { return iframe.contentWindow.document; };
6880
6881
      // Catch js errors and pass them to the parent's onerror event
6882
      // addEventListener("error") doesn't work properly in some browsers
6883
      // TODO: apparently this doesn't work in IE9!
6884
      iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
6885
        throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
6886
      };
6887
6888
      if (!wysihtml5.browser.supportsSandboxedIframes()) {
6889
        // Unset a bunch of sensitive variables
6890
        // Please note: This isn't hack safe!
6891
        // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
6892
        // IE is secure though, which is the most important thing, since IE is the only browser, who
6893
        // takes over scripts & styles into contentEditable elements when copied from external websites
6894
        // or applications (Microsoft Word, ...)
6895
        var i, length;
6896
        for (i=0, length=windowProperties.length; i<length; i++) {
6897
          this._unset(iframeWindow, windowProperties[i]);
6898
        }
6899
        for (i=0, length=windowProperties2.length; i<length; i++) {
6900
          this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION);
6901
        }
6902
        for (i=0, length=documentProperties.length; i<length; i++) {
6903
          this._unset(iframeDocument, documentProperties[i]);
6904
        }
6905
        // This doesn't work in Safari 5
6906
        // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
6907
        this._unset(iframeDocument, "cookie", "", true);
6908
      }
6909
6910
      this.loaded = true;
6911
6912
      // Trigger the callback
6913
      setTimeout(function() { that.callback(that); }, 0);
6914
    },
6915
6916
    _getHtml: function(templateVars) {
6917
      var stylesheets = templateVars.stylesheets,
6918
          html        = "",
6919
          i           = 0,
6920
          length;
6921
      stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
6922
      if (stylesheets) {
6923
        length = stylesheets.length;
6924
        for (; i<length; i++) {
6925
          html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
6926
        }
6927
      }
6928
      templateVars.stylesheets = html;
6929
6930
      return wysihtml5.lang.string(
6931
        '<!DOCTYPE html><html><head>'
6932
        + '<meta charset="#{charset}">#{stylesheets}</head>'
6933
        + '<body></body></html>'
6934
      ).interpolate(templateVars);
6935
    },
6936
6937
    /**
6938
     * Method to unset/override existing variables
6939
     * @example
6940
     *    // Make cookie unreadable and unwritable
6941
     *    this._unset(document, "cookie", "", true);
6942
     */
6943
    _unset: function(object, property, value, setter) {
6944
      try { object[property] = value; } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
6945
6946
      try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
6947
      if (setter) {
6948
        try { object.__defineSetter__(property, function() {}); } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
6949
      }
6950
6951
      if (!wysihtml5.browser.crashesWhenDefineProperty(property)) {
6952
        try {
6953
          var config = {
6954
            get: function() { return value; }
6955
          };
6956
          if (setter) {
6957
            config.set = function() {};
6958
          }
6959
          Object.defineProperty(object, property, config);
6960
        } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
6961
      }
6962
    }
6963
  });
6964
})(wysihtml5);
6965
;(function(wysihtml5) {
6966
  var doc = document;
6967
  wysihtml5.dom.ContentEditableArea = Base.extend({
6968
      getContentEditable: function() {
6969
        return this.element;
6970
      },
6971
6972
      getWindow: function() {
6973
        return this.element.ownerDocument.defaultView;
6974
      },
6975
6976
      getDocument: function() {
6977
        return this.element.ownerDocument;
6978
      },
6979
6980
      constructor: function(readyCallback, config, contentEditable) {
6981
        this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
6982
        this.config   = wysihtml5.lang.object({}).merge(config).get();
6983
        if (contentEditable) {
6984
            this.element = this._bindElement(contentEditable);
6985
        } else {
6986
            this.element = this._createElement();
6987
        }
6988
      },
6989
6990
      // creates a new contenteditable and initiates it
6991
      _createElement: function() {
6992
        var element = doc.createElement("div");
6993
        element.className = "wysihtml5-sandbox";
6994
        this._loadElement(element);
6995
        return element;
6996
      },
6997
6998
      // initiates an allready existent contenteditable
6999
      _bindElement: function(contentEditable) {
7000
        contentEditable.className = (contentEditable.className && contentEditable.className != '') ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
7001
        this._loadElement(contentEditable, true);
7002
        return contentEditable;
7003
      },
7004
7005
      _loadElement: function(element, contentExists) {
7006
          var that = this;
7007
        if (!contentExists) {
7008
            var sandboxHtml = this._getHtml();
7009
            element.innerHTML = sandboxHtml;
7010
        }
7011
7012
        this.getWindow = function() { return element.ownerDocument.defaultView; };
7013
        this.getDocument = function() { return element.ownerDocument; };
7014
7015
        // Catch js errors and pass them to the parent's onerror event
7016
        // addEventListener("error") doesn't work properly in some browsers
7017
        // TODO: apparently this doesn't work in IE9!
7018
        // TODO: figure out and bind the errors logic for contenteditble mode
7019
        /*iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
7020
          throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
7021
        }
7022
        */
7023
        this.loaded = true;
7024
        // Trigger the callback
7025
        setTimeout(function() { that.callback(that); }, 0);
7026
      },
7027
7028
      _getHtml: function(templateVars) {
0 ignored issues
show
Unused Code introduced by
The parameter templateVars is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
7029
        return '';
7030
      }
7031
7032
  });
7033
})(wysihtml5);
7034
;(function() {
7035
  var mapping = {
7036
    "className": "class"
7037
  };
7038
  wysihtml5.dom.setAttributes = function(attributes) {
7039
    return {
7040
      on: function(element) {
7041
        for (var i in attributes) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
7042
          element.setAttribute(mapping[i] || i, attributes[i]);
7043
        }
7044
      }
7045
    };
7046
  };
7047
})();
7048
;wysihtml5.dom.setStyles = function(styles) {
7049
  return {
7050
    on: function(element) {
7051
      var style = element.style;
7052
      if (typeof(styles) === "string") {
7053
        style.cssText += ";" + styles;
7054
        return;
7055
      }
7056
      for (var i in styles) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
7057
        if (i === "float") {
7058
          style.cssFloat = styles[i];
7059
          style.styleFloat = styles[i];
7060
        } else {
7061
          style[i] = styles[i];
7062
        }
7063
      }
7064
    }
7065
  };
7066
};
7067
;/**
7068
 * Simulate HTML5 placeholder attribute
7069
 *
7070
 * Needed since
7071
 *    - div[contentEditable] elements don't support it
7072
 *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all
7073
 *
7074
 * @param {Object} parent Instance of main wysihtml5.Editor class
7075
 * @param {Element} view Instance of wysihtml5.views.* class
7076
 * @param {String} placeholderText
7077
 *
7078
 * @example
7079
 *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
7080
 */
7081
(function(dom) {
7082
  dom.simulatePlaceholder = function(editor, view, placeholderText) {
7083
    var CLASS_NAME = "placeholder",
7084
        unset = function() {
7085
          var composerIsVisible   = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
7086
          if (view.hasPlaceholderSet()) {
7087
            view.clear();
7088
            view.element.focus();
7089
            if (composerIsVisible ) {
7090
              setTimeout(function() {
7091
                var sel = view.selection.getSelection();
7092
                if (!sel.focusNode || !sel.anchorNode) {
7093
                  view.selection.selectNode(view.element.firstChild || view.element);
7094
                }
7095
              }, 0);
7096
            }
7097
          }
7098
          view.placeholderSet = false;
7099
          dom.removeClass(view.element, CLASS_NAME);
7100
        },
7101
        set = function() {
7102
          if (view.isEmpty()) {
7103
            view.placeholderSet = true;
7104
            view.setValue(placeholderText);
7105
            dom.addClass(view.element, CLASS_NAME);
7106
          }
7107
        };
7108
7109
    editor
7110
      .on("set_placeholder", set)
7111
      .on("unset_placeholder", unset)
7112
      .on("focus:composer", unset)
7113
      .on("paste:composer", unset)
7114
      .on("blur:composer", set);
7115
7116
    set();
7117
  };
7118
})(wysihtml5.dom);
7119
;(function(dom) {
7120
  var documentElement = document.documentElement;
7121
  if ("textContent" in documentElement) {
7122
    dom.setTextContent = function(element, text) {
7123
      element.textContent = text;
7124
    };
7125
7126
    dom.getTextContent = function(element) {
7127
      return element.textContent;
7128
    };
7129
  } else if ("innerText" in documentElement) {
7130
    dom.setTextContent = function(element, text) {
7131
      element.innerText = text;
7132
    };
7133
7134
    dom.getTextContent = function(element) {
7135
      return element.innerText;
7136
    };
7137
  } else {
7138
    dom.setTextContent = function(element, text) {
7139
      element.nodeValue = text;
7140
    };
7141
7142
    dom.getTextContent = function(element) {
7143
      return element.nodeValue;
7144
    };
7145
  }
7146
})(wysihtml5.dom);
7147
7148
;/**
7149
 * Get a set of attribute from one element
7150
 *
7151
 * IE gives wrong results for hasAttribute/getAttribute, for example:
7152
 *    var td = document.createElement("td");
7153
 *    td.getAttribute("rowspan"); // => "1" in IE
7154
 *
7155
 * Therefore we have to check the element's outerHTML for the attribute
7156
*/
7157
7158
wysihtml5.dom.getAttribute = function(node, attributeName) {
7159
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
7160
  attributeName = attributeName.toLowerCase();
7161
  var nodeName = node.nodeName;
7162
  if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
7163
    // Get 'src' attribute value via object property since this will always contain the
7164
    // full absolute url (http://...)
7165
    // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
7166
    // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
7167
    return node.src;
7168
  } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
7169
    // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
7170
    var outerHTML      = node.outerHTML.toLowerCase(),
7171
        // TODO: This might not work for attributes without value: <input disabled>
7172
        hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
7173
7174
    return hasAttribute ? node.getAttribute(attributeName) : null;
7175
  } else{
7176
    return node.getAttribute(attributeName);
7177
  }
7178
};
7179
;(function(wysihtml5) {
7180
7181
    var api = wysihtml5.dom;
7182
7183
    var MapCell = function(cell) {
7184
      this.el = cell;
7185
      this.isColspan= false;
7186
      this.isRowspan= false;
7187
      this.firstCol= true;
7188
      this.lastCol= true;
7189
      this.firstRow= true;
7190
      this.lastRow= true;
7191
      this.isReal= true;
7192
      this.spanCollection= [];
7193
      this.modified = false;
7194
    };
7195
7196
    var TableModifyerByCell = function (cell, table) {
7197
        if (cell) {
7198
            this.cell = cell;
7199
            this.table = api.getParentElement(cell, { nodeName: ["TABLE"] });
7200
        } else if (table) {
7201
            this.table = table;
7202
            this.cell = this.table.querySelectorAll('th, td')[0];
7203
        }
7204
    };
7205
7206
    function queryInList(list, query) {
7207
        var ret = [],
7208
            q;
7209
        for (var e = 0, len = list.length; e < len; e++) {
7210
            q = list[e].querySelectorAll(query);
7211
            if (q) {
7212
                for(var i = q.length; i--; ret.unshift(q[i]));
0 ignored issues
show
introduced by
The for loop does not have a body. Maybe you have misplaced a semicolon. If you do wish to have a loop without a body, use an empty body {}.
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
7213
            }
7214
        }
7215
        return ret;
7216
    }
7217
7218
    function removeElement(el) {
7219
        el.parentNode.removeChild(el);
7220
    }
7221
7222
    function insertAfter(referenceNode, newNode) {
7223
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
7224
    }
7225
7226
    function nextNode(node, tag) {
7227
        var element = node.nextSibling;
7228
        while (element.nodeType !=1) {
0 ignored issues
show
Best Practice introduced by
Comparing element.nodeType to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
7229
            element = element.nextSibling;
7230
            if (!tag || tag == element.tagName.toLowerCase()) {
7231
                return element;
7232
            }
7233
        }
7234
        return null;
7235
    }
7236
7237
    TableModifyerByCell.prototype = {
7238
7239
        addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) {
7240
            var spanCollect = [],
7241
                rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0),
7242
                cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0);
7243
7244
            for (var rr = r; rr <= rmax; rr++) {
7245
                if (typeof map[rr] == "undefined") { map[rr] = []; }
7246
                for (var cc = c; cc <= cmax; cc++) {
7247
                    map[rr][cc] = new MapCell(cell);
7248
                    map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1);
7249
                    map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1);
7250
                    map[rr][cc].firstCol = cc == c;
7251
                    map[rr][cc].lastCol = cc == cmax;
7252
                    map[rr][cc].firstRow = rr == r;
7253
                    map[rr][cc].lastRow = rr == rmax;
7254
                    map[rr][cc].isReal = cc == c && rr == r;
7255
                    map[rr][cc].spanCollection = spanCollect;
7256
7257
                    spanCollect.push(map[rr][cc]);
7258
                }
7259
            }
7260
        },
7261
7262
        setCellAsModified: function(cell) {
7263
            cell.modified = true;
7264
            if (cell.spanCollection.length > 0) {
7265
              for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) {
7266
                cell.spanCollection[s].modified = true;
7267
              }
7268
            }
7269
        },
7270
7271
        setTableMap: function() {
7272
            var map = [];
7273
            var tableRows = this.getTableRows(),
7274
                ridx, row, cells, cidx, cell,
7275
                c,
7276
                cspan, rspan;
7277
7278
            for (ridx = 0; ridx < tableRows.length; ridx++) {
7279
                row = tableRows[ridx];
7280
                cells = this.getRowCells(row);
7281
                c = 0;
7282
                if (typeof map[ridx] == "undefined") { map[ridx] = []; }
7283
                for (cidx = 0; cidx < cells.length; cidx++) {
7284
                    cell = cells[cidx];
7285
7286
                    // If cell allready set means it is set by col or rowspan,
7287
                    // so increase cols index until free col is found
7288
                    while (typeof map[ridx][c] != "undefined") { c++; }
7289
7290
                    cspan = api.getAttribute(cell, 'colspan');
7291
                    rspan = api.getAttribute(cell, 'rowspan');
7292
7293
                    if (cspan || rspan) {
7294
                        this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan);
7295
                        c = c + ((cspan) ? parseInt(cspan, 10) : 1);
7296
                    } else {
7297
                        map[ridx][c] = new MapCell(cell);
7298
                        c++;
7299
                    }
7300
                }
7301
            }
7302
            this.map = map;
7303
            return map;
7304
        },
7305
7306
        getRowCells: function(row) {
7307
            var inlineTables = this.table.querySelectorAll('table'),
7308
                inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [],
7309
                allCells = row.querySelectorAll('th, td'),
7310
                tableCells = (inlineCells.length > 0) ? wysihtml5.lang.array(allCells).without(inlineCells) : allCells;
7311
7312
            return tableCells;
7313
        },
7314
7315
        getTableRows: function() {
7316
          var inlineTables = this.table.querySelectorAll('table'),
7317
              inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [],
7318
              allRows = this.table.querySelectorAll('tr'),
7319
              tableRows = (inlineRows.length > 0) ? wysihtml5.lang.array(allRows).without(inlineRows) : allRows;
7320
7321
          return tableRows;
7322
        },
7323
7324
        getMapIndex: function(cell) {
7325
          var r_length = this.map.length,
7326
              c_length = (this.map && this.map[0]) ? this.map[0].length : 0;
7327
7328
          for (var r_idx = 0;r_idx < r_length; r_idx++) {
7329
              for (var c_idx = 0;c_idx < c_length; c_idx++) {
7330
                  if (this.map[r_idx][c_idx].el === cell) {
7331
                      return {'row': r_idx, 'col': c_idx};
7332
                  }
7333
              }
7334
          }
7335
          return false;
7336
        },
7337
7338
        getElementAtIndex: function(idx) {
7339
            this.setTableMap();
7340
            if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) {
7341
                return this.map[idx.row][idx.col].el;
7342
            }
7343
            return null;
7344
        },
7345
7346
        getMapElsTo: function(to_cell) {
7347
            var els = [];
7348
            this.setTableMap();
7349
            this.idx_start = this.getMapIndex(this.cell);
7350
            this.idx_end = this.getMapIndex(to_cell);
7351
7352
            // switch indexes if start is bigger than end
7353
            if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
7354
                var temp_idx = this.idx_start;
7355
                this.idx_start = this.idx_end;
7356
                this.idx_end = temp_idx;
7357
            }
7358
            if (this.idx_start.col > this.idx_end.col) {
7359
                var temp_cidx = this.idx_start.col;
7360
                this.idx_start.col = this.idx_end.col;
7361
                this.idx_end.col = temp_cidx;
7362
            }
7363
7364
            if (this.idx_start != null && this.idx_end != null) {
0 ignored issues
show
Best Practice introduced by
Comparing this.idx_start to null using the != operator is not safe. Consider using !== instead.
Loading history...
Best Practice introduced by
Comparing this.idx_end to null using the != operator is not safe. Consider using !== instead.
Loading history...
7365
                for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7366
                    for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7367
                        els.push(this.map[row][col].el);
7368
                    }
7369
                }
7370
            }
7371
            return els;
7372
        },
7373
7374
        orderSelectionEnds: function(secondcell) {
7375
            this.setTableMap();
7376
            this.idx_start = this.getMapIndex(this.cell);
7377
            this.idx_end = this.getMapIndex(secondcell);
7378
7379
            // switch indexes if start is bigger than end
7380
            if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
7381
                var temp_idx = this.idx_start;
7382
                this.idx_start = this.idx_end;
7383
                this.idx_end = temp_idx;
7384
            }
7385
            if (this.idx_start.col > this.idx_end.col) {
7386
                var temp_cidx = this.idx_start.col;
7387
                this.idx_start.col = this.idx_end.col;
7388
                this.idx_end.col = temp_cidx;
7389
            }
7390
7391
            return {
7392
                "start": this.map[this.idx_start.row][this.idx_start.col].el,
7393
                "end": this.map[this.idx_end.row][this.idx_end.col].el
7394
            };
7395
        },
7396
7397
        createCells: function(tag, nr, attrs) {
7398
            var doc = this.table.ownerDocument,
7399
                frag = doc.createDocumentFragment(),
7400
                cell;
7401
            for (var i = 0; i < nr; i++) {
7402
                cell = doc.createElement(tag);
7403
7404
                if (attrs) {
7405
                    for (var attr in attrs) {
7406
                        if (attrs.hasOwnProperty(attr)) {
7407
                            cell.setAttribute(attr, attrs[attr]);
7408
                        }
7409
                    }
7410
                }
7411
7412
                // add non breaking space
7413
                cell.appendChild(document.createTextNode("\u00a0"));
7414
7415
                frag.appendChild(cell);
7416
            }
7417
            return frag;
7418
        },
7419
7420
        // Returns next real cell (not part of spanned cell unless first) on row if selected index is not real. I no real cells -1 will be returned
7421
        correctColIndexForUnreals: function(col, row) {
7422
            var r = this.map[row],
7423
                corrIdx = -1;
7424
            for (var i = 0, max = col; i < col; i++) {
0 ignored issues
show
Unused Code introduced by
The variable max seems to be never used. Consider removing it.
Loading history...
7425
                if (r[i].isReal){
7426
                    corrIdx++;
7427
                }
7428
            }
7429
            return corrIdx;
7430
        },
7431
7432
        getLastNewCellOnRow: function(row, rowLimit) {
7433
            var cells = this.getRowCells(row),
7434
                cell, idx;
7435
7436
            for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) {
7437
                cell = cells[cidx];
7438
                idx = this.getMapIndex(cell);
7439
                if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) {
7440
                    return cell;
7441
                }
7442
            }
7443
            return null;
7444
        },
7445
7446
        removeEmptyTable: function() {
7447
            var cells = this.table.querySelectorAll('td, th');
7448
            if (!cells || cells.length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing cells.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
7449
                removeElement(this.table);
7450
                return true;
7451
            } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
7452
                return false;
7453
            }
7454
        },
7455
7456
        // Splits merged cell on row to unique cells
7457
        splitRowToCells: function(cell) {
7458
            if (cell.isColspan) {
7459
                var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10),
7460
                    cType = cell.el.tagName.toLowerCase();
7461
                if (colspan > 1) {
7462
                    var newCells = this.createCells(cType, colspan -1);
7463
                    insertAfter(cell.el, newCells);
7464
                }
7465
                cell.el.removeAttribute('colspan');
7466
            }
7467
        },
7468
7469
        getRealRowEl: function(force, idx) {
7470
            var r = null,
7471
                c = null;
0 ignored issues
show
Unused Code introduced by
The assignment to c seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
7472
7473
            idx = idx || this.idx;
7474
7475
            for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) {
7476
                c = this.map[idx.row][cidx];
7477
                if (c.isReal) {
7478
                    r = api.getParentElement(c.el, { nodeName: ["TR"] });
7479
                    if (r) {
7480
                        return r;
7481
                    }
7482
                }
7483
            }
7484
7485
            if (r === null && force) {
7486
                r = api.getParentElement(this.map[idx.row][idx.col].el, { nodeName: ["TR"] }) || null;
7487
            }
7488
7489
            return r;
7490
        },
7491
7492
        injectRowAt: function(row, col, colspan, cType, c) {
7493
            var r = this.getRealRowEl(false, {'row': row, 'col': col}),
7494
                new_cells = this.createCells(cType, colspan);
7495
7496
            if (r) {
7497
                var n_cidx = this.correctColIndexForUnreals(col, row);
7498
                if (n_cidx >= 0) {
7499
                    insertAfter(this.getRowCells(r)[n_cidx], new_cells);
7500
                } else {
7501
                    r.insertBefore(new_cells, r.firstChild);
7502
                }
7503
            } else {
7504
                var rr = this.table.ownerDocument.createElement('tr');
7505
                rr.appendChild(new_cells);
7506
                insertAfter(api.getParentElement(c.el, { nodeName: ["TR"] }), rr);
7507
            }
7508
        },
7509
7510
        canMerge: function(to) {
7511
            this.to = to;
7512
            this.setTableMap();
7513
            this.idx_start = this.getMapIndex(this.cell);
7514
            this.idx_end = this.getMapIndex(this.to);
7515
7516
            // switch indexes if start is bigger than end
7517
            if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) {
7518
                var temp_idx = this.idx_start;
7519
                this.idx_start = this.idx_end;
7520
                this.idx_end = temp_idx;
7521
            }
7522
            if (this.idx_start.col > this.idx_end.col) {
7523
                var temp_cidx = this.idx_start.col;
7524
                this.idx_start.col = this.idx_end.col;
7525
                this.idx_end.col = temp_cidx;
7526
            }
7527
7528
            for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7529
                for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7530
                    if (this.map[row][col].isColspan || this.map[row][col].isRowspan) {
7531
                        return false;
7532
                    }
7533
                }
7534
            }
7535
            return true;
7536
        },
7537
7538
        decreaseCellSpan: function(cell, span) {
7539
            var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1;
7540
            if (nr >= 1) {
7541
                cell.el.setAttribute(span, nr);
7542
            } else {
7543
                cell.el.removeAttribute(span);
7544
                if (span == 'colspan') {
7545
                    cell.isColspan = false;
7546
                }
7547
                if (span == 'rowspan') {
7548
                    cell.isRowspan = false;
7549
                }
7550
                cell.firstCol = true;
7551
                cell.lastCol = true;
7552
                cell.firstRow = true;
7553
                cell.lastRow = true;
7554
                cell.isReal = true;
7555
            }
7556
        },
7557
7558
        removeSurplusLines: function() {
7559
            var row, cell, ridx, rmax, cidx, cmax, allRowspan;
7560
7561
            this.setTableMap();
7562
            if (this.map) {
7563
                ridx = 0;
7564
                rmax = this.map.length;
7565
                for (;ridx < rmax; ridx++) {
7566
                    row = this.map[ridx];
7567
                    allRowspan = true;
7568
                    cidx = 0;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable cidx here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
7569
                    cmax = row.length;
7570
                    for (; cidx < cmax; cidx++) {
7571
                        cell = row[cidx];
7572
                        if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) {
7573
                            allRowspan = false;
7574
                            break;
7575
                        }
7576
                    }
7577
                    if (allRowspan) {
7578
                        cidx = 0;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable cidx here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
7579
                        for (; cidx < cmax; cidx++) {
7580
                            this.decreaseCellSpan(row[cidx], 'rowspan');
7581
                        }
7582
                    }
7583
                }
7584
7585
                // remove rows without cells
7586
                var tableRows = this.getTableRows();
7587
                ridx = 0;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable ridx here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
7588
                rmax = tableRows.length;
7589
                for (;ridx < rmax; ridx++) {
7590
                    row = tableRows[ridx];
7591
                    if (row.childNodes.length == 0 && (/^\s*$/.test(row.textContent || row.innerText))) {
0 ignored issues
show
Best Practice introduced by
Comparing row.childNodes.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
7592
                        removeElement(row);
7593
                    }
7594
                }
7595
            }
7596
        },
7597
7598
        fillMissingCells: function() {
7599
            var r_max = 0,
7600
                c_max = 0,
7601
                prevcell = null;
0 ignored issues
show
Unused Code introduced by
The assignment to prevcell seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
7602
7603
            this.setTableMap();
7604
            if (this.map) {
7605
7606
                // find maximal dimensions of broken table
7607
                r_max = this.map.length;
7608
                for (var ridx = 0; ridx < r_max; ridx++) {
7609
                    if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; }
7610
                }
7611
7612
                for (var row = 0; row < r_max; row++) {
7613
                    for (var col = 0; col < c_max; col++) {
7614
                        if (this.map[row] && !this.map[row][col]) {
7615
                            if (col > 0) {
7616
                                this.map[row][col] = new MapCell(this.createCells('td', 1));
7617
                                prevcell = this.map[row][col-1];
7618
                                if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom
7619
                                    insertAfter(this.map[row][col-1].el, this.map[row][col].el);
7620
                                }
7621
                            }
7622
                        }
7623
                    }
7624
                }
7625
            }
7626
        },
7627
7628
        rectify: function() {
7629
            if (!this.removeEmptyTable()) {
7630
                this.removeSurplusLines();
7631
                this.fillMissingCells();
7632
                return true;
7633
            } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
7634
                return false;
7635
            }
7636
        },
7637
7638
        unmerge: function() {
7639
            if (this.rectify()) {
7640
                this.setTableMap();
7641
                this.idx = this.getMapIndex(this.cell);
7642
7643
                if (this.idx) {
7644
                    var thisCell = this.map[this.idx.row][this.idx.col],
7645
                        colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1,
7646
                        cType = thisCell.el.tagName.toLowerCase();
7647
7648
                    if (thisCell.isRowspan) {
7649
                        var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10);
7650
                        if (rowspan > 1) {
7651
                            for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){
7652
                                this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell);
7653
                            }
7654
                        }
7655
                        thisCell.el.removeAttribute('rowspan');
7656
                    }
7657
                    this.splitRowToCells(thisCell);
7658
                }
7659
            }
7660
        },
7661
7662
        // merges cells from start cell (defined in creating obj) to "to" cell
7663
        merge: function(to) {
7664
            if (this.rectify()) {
7665
                if (this.canMerge(to)) {
7666
                    var rowspan = this.idx_end.row - this.idx_start.row + 1,
7667
                        colspan = this.idx_end.col - this.idx_start.col + 1;
7668
7669
                    for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7670
                        for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7671
7672
                            if (row == this.idx_start.row && col == this.idx_start.col) {
7673
                                if (rowspan > 1) {
7674
                                    this.map[row][col].el.setAttribute('rowspan', rowspan);
7675
                                }
7676
                                if (colspan > 1) {
7677
                                    this.map[row][col].el.setAttribute('colspan', colspan);
7678
                                }
7679
                            } else {
7680
                                // transfer content
7681
                                if (!(/^\s*<br\/?>\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) {
7682
                                    this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML;
7683
                                }
7684
                                removeElement(this.map[row][col].el);
7685
                            }
7686
                        }
7687
                    }
7688
                    this.rectify();
7689
                } else {
7690
                    if (window.console) {
7691
                        console.log('Do not know how to merge allready merged cells.');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
7692
                    }
7693
                }
7694
            }
7695
        },
7696
7697
        // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell)
7698
        // Cell is moved to next row (if it is real)
7699
        collapseCellToNextRow: function(cell) {
7700
            var cellIdx = this.getMapIndex(cell.el),
7701
                newRowIdx = cellIdx.row + 1,
7702
                newIdx = {'row': newRowIdx, 'col': cellIdx.col};
7703
7704
            if (newRowIdx < this.map.length) {
7705
7706
                var row = this.getRealRowEl(false, newIdx);
7707
                if (row !== null) {
7708
                    var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row);
7709
                    if (n_cidx >= 0) {
7710
                        insertAfter(this.getRowCells(row)[n_cidx], cell.el);
7711
                    } else {
7712
                        var lastCell = this.getLastNewCellOnRow(row, newRowIdx);
7713
                        if (lastCell !== null) {
7714
                            insertAfter(lastCell, cell.el);
7715
                        } else {
7716
                            row.insertBefore(cell.el, row.firstChild);
7717
                        }
7718
                    }
7719
                    if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7720
                        cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7721
                    } else {
7722
                        cell.el.removeAttribute('rowspan');
7723
                    }
7724
                }
7725
            }
7726
        },
7727
7728
        // Removes a cell when removing a row
7729
        // If is rowspan cell then decreases the rowspan
7730
        // and moves cell to next row if needed (is first cell of rowspan)
7731
        removeRowCell: function(cell) {
7732
            if (cell.isReal) {
7733
               if (cell.isRowspan) {
7734
                   this.collapseCellToNextRow(cell);
7735
               } else {
7736
                   removeElement(cell.el);
7737
               }
7738
            } else {
7739
                if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7740
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7741
                } else {
7742
                    cell.el.removeAttribute('rowspan');
7743
                }
7744
            }
7745
        },
7746
7747
        getRowElementsByCell: function() {
7748
            var cells = [];
7749
            this.setTableMap();
7750
            this.idx = this.getMapIndex(this.cell);
7751
            if (this.idx !== false) {
7752
                var modRow = this.map[this.idx.row];
7753
                for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7754
                    if (modRow[cidx].isReal) {
7755
                        cells.push(modRow[cidx].el);
7756
                    }
7757
                }
7758
            }
7759
            return cells;
7760
        },
7761
7762
        getColumnElementsByCell: function() {
7763
            var cells = [];
7764
            this.setTableMap();
7765
            this.idx = this.getMapIndex(this.cell);
7766
            if (this.idx !== false) {
7767
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7768
                    if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) {
7769
                        cells.push(this.map[ridx][this.idx.col].el);
7770
                    }
7771
                }
7772
            }
7773
            return cells;
7774
        },
7775
7776
        // Removes the row of selected cell
7777
        removeRow: function() {
7778
            var oldRow = api.getParentElement(this.cell, { nodeName: ["TR"] });
7779
            if (oldRow) {
7780
                this.setTableMap();
7781
                this.idx = this.getMapIndex(this.cell);
7782
                if (this.idx !== false) {
7783
                    var modRow = this.map[this.idx.row];
7784
                    for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7785
                        if (!modRow[cidx].modified) {
7786
                            this.setCellAsModified(modRow[cidx]);
7787
                            this.removeRowCell(modRow[cidx]);
7788
                        }
7789
                    }
7790
                }
7791
                removeElement(oldRow);
7792
            }
7793
        },
7794
7795
        removeColCell: function(cell) {
7796
            if (cell.isColspan) {
7797
                if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) {
7798
                    cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1);
7799
                } else {
7800
                    cell.el.removeAttribute('colspan');
7801
                }
7802
            } else if (cell.isReal) {
7803
                removeElement(cell.el);
7804
            }
7805
        },
7806
7807
        removeColumn: function() {
7808
            this.setTableMap();
7809
            this.idx = this.getMapIndex(this.cell);
7810
            if (this.idx !== false) {
7811
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7812
                    if (!this.map[ridx][this.idx.col].modified) {
7813
                        this.setCellAsModified(this.map[ridx][this.idx.col]);
7814
                        this.removeColCell(this.map[ridx][this.idx.col]);
7815
                    }
7816
                }
7817
            }
7818
        },
7819
7820
        // removes row or column by selected cell element
7821
        remove: function(what) {
7822
            if (this.rectify()) {
7823
                switch (what) {
7824
                    case 'row':
7825
                        this.removeRow();
7826
                    break;
7827
                    case 'column':
7828
                        this.removeColumn();
7829
                    break;
7830
                }
7831
                this.rectify();
7832
            }
7833
        },
7834
7835
        addRow: function(where) {
7836
            var doc = this.table.ownerDocument;
7837
7838
            this.setTableMap();
7839
            this.idx = this.getMapIndex(this.cell);
7840
            if (where == "below" && api.getAttribute(this.cell, 'rowspan')) {
7841
                this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1;
7842
            }
7843
7844
            if (this.idx !== false) {
7845
                var modRow = this.map[this.idx.row],
7846
                    newRow = doc.createElement('tr');
7847
7848
                for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) {
7849
                    if (!modRow[ridx].modified) {
7850
                        this.setCellAsModified(modRow[ridx]);
7851
                        this.addRowCell(modRow[ridx], newRow, where);
7852
                    }
7853
                }
7854
7855
                switch (where) {
7856
                    case 'below':
7857
                        insertAfter(this.getRealRowEl(true), newRow);
7858
                    break;
7859
                    case 'above':
7860
                        var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { nodeName: ["TR"] });
7861
                        if (cr) {
7862
                            cr.parentNode.insertBefore(newRow, cr);
7863
                        }
7864
                    break;
7865
                }
7866
            }
7867
        },
7868
7869
        addRowCell: function(cell, row, where) {
7870
            var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null;
7871
            if (cell.isReal) {
7872
                if (where != 'above' && cell.isRowspan) {
7873
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1);
7874
                } else {
7875
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
7876
                }
7877
            } else {
7878
                if (where != 'above' && cell.isRowspan && cell.lastRow) {
7879
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
7880
                } else if (c.isRowspan) {
0 ignored issues
show
Bug introduced by
The variable c seems to be never declared. If this is a global, consider adding a /** global: c */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
7881
                    cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1);
7882
                }
7883
            }
7884
        },
7885
7886
        add: function(where) {
7887
            if (this.rectify()) {
7888
                if (where == 'below' || where == 'above') {
7889
                    this.addRow(where);
7890
                }
7891
                if (where == 'before' || where == 'after') {
7892
                    this.addColumn(where);
7893
                }
7894
            }
7895
        },
7896
7897
        addColCell: function (cell, ridx, where) {
7898
            var doAdd,
7899
                cType = cell.el.tagName.toLowerCase();
7900
7901
            // defines add cell vs expand cell conditions
7902
            // true means add
7903
            switch (where) {
7904
                case "before":
7905
                    doAdd = (!cell.isColspan || cell.firstCol);
7906
                break;
7907
                case "after":
7908
                    doAdd = (!cell.isColspan || cell.lastCol || (cell.isColspan && c.el == this.cell));
0 ignored issues
show
Bug introduced by
The variable c seems to be never declared. If this is a global, consider adding a /** global: c */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
7909
                break;
7910
            }
7911
7912
            if (doAdd){
7913
                // adds a cell before or after current cell element
7914
                switch (where) {
7915
                    case "before":
7916
                        cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el);
7917
                    break;
7918
                    case "after":
7919
                        insertAfter(cell.el, this.createCells(cType, 1));
7920
                    break;
7921
                }
7922
7923
                // handles if cell has rowspan
7924
                if (cell.isRowspan) {
7925
                    this.handleCellAddWithRowspan(cell, ridx+1, where);
7926
                }
7927
7928
            } else {
7929
                // expands cell
7930
                cell.el.setAttribute('colspan',  parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1);
7931
            }
7932
        },
7933
7934
        addColumn: function(where) {
7935
            var row, modCell;
7936
7937
            this.setTableMap();
7938
            this.idx = this.getMapIndex(this.cell);
7939
            if (where == "after" && api.getAttribute(this.cell, 'colspan')) {
7940
              this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1;
7941
            }
7942
7943
            if (this.idx !== false) {
7944
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) {
7945
                    row = this.map[ridx];
7946
                    if (row[this.idx.col]) {
7947
                        modCell = row[this.idx.col];
7948
                        if (!modCell.modified) {
7949
                            this.setCellAsModified(modCell);
7950
                            this.addColCell(modCell, ridx , where);
7951
                        }
7952
                    }
7953
                }
7954
            }
7955
        },
7956
7957
        handleCellAddWithRowspan: function (cell, ridx, where) {
7958
            var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1,
7959
                crow = api.getParentElement(cell.el, { nodeName: ["TR"] }),
7960
                cType = cell.el.tagName.toLowerCase(),
7961
                cidx, temp_r_cells,
7962
                doc = this.table.ownerDocument,
7963
                nrow;
7964
7965
            for (var i = 0; i < addRowsNr; i++) {
7966
                cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i));
7967
                crow = nextNode(crow, 'tr');
7968
                if (crow) {
7969
                    if (cidx > 0) {
7970
                        switch (where) {
7971
                            case "before":
7972
                                temp_r_cells = this.getRowCells(crow);
7973
                                if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) {
7974
                                     insertAfter(temp_r_cells[cidx], this.createCells(cType, 1));
7975
                                } else {
7976
                                    temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]);
7977
                                }
7978
7979
                            break;
7980
                            case "after":
7981
                                insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1));
7982
                            break;
7983
                        }
7984
                    } else {
7985
                        crow.insertBefore(this.createCells(cType, 1), crow.firstChild);
7986
                    }
7987
                } else {
7988
                    nrow = doc.createElement('tr');
7989
                    nrow.appendChild(this.createCells(cType, 1));
7990
                    this.table.appendChild(nrow);
7991
                }
7992
            }
7993
        }
7994
    };
7995
7996
    api.table = {
7997
        getCellsBetween: function(cell1, cell2) {
7998
            var c1 = new TableModifyerByCell(cell1);
7999
            return c1.getMapElsTo(cell2);
8000
        },
8001
8002
        addCells: function(cell, where) {
8003
            var c = new TableModifyerByCell(cell);
8004
            c.add(where);
8005
        },
8006
8007
        removeCells: function(cell, what) {
8008
            var c = new TableModifyerByCell(cell);
8009
            c.remove(what);
8010
        },
8011
8012
        mergeCellsBetween: function(cell1, cell2) {
8013
            var c1 = new TableModifyerByCell(cell1);
8014
            c1.merge(cell2);
8015
        },
8016
8017
        unmergeCell: function(cell) {
8018
            var c = new TableModifyerByCell(cell);
8019
            c.unmerge();
8020
        },
8021
8022
        orderSelectionEnds: function(cell, cell2) {
8023
            var c = new TableModifyerByCell(cell);
8024
            return c.orderSelectionEnds(cell2);
8025
        },
8026
8027
        indexOf: function(cell) {
8028
            var c = new TableModifyerByCell(cell);
8029
            c.setTableMap();
8030
            return c.getMapIndex(cell);
8031
        },
8032
8033
        findCell: function(table, idx) {
8034
            var c = new TableModifyerByCell(null, table);
8035
            return c.getElementAtIndex(idx);
8036
        },
8037
8038
        findRowByCell: function(cell) {
8039
            var c = new TableModifyerByCell(cell);
8040
            return c.getRowElementsByCell();
8041
        },
8042
8043
        findColumnByCell: function(cell) {
8044
            var c = new TableModifyerByCell(cell);
8045
            return c.getColumnElementsByCell();
8046
        },
8047
8048
        canMerge: function(cell1, cell2) {
8049
            var c = new TableModifyerByCell(cell1);
8050
            return c.canMerge(cell2);
8051
        }
8052
    };
8053
8054
8055
8056
})(wysihtml5);
8057
;// does a selector query on element or array of elements
8058
8059
wysihtml5.dom.query = function(elements, query) {
8060
    var ret = [],
8061
        q;
8062
8063
    if (elements.nodeType) {
8064
        elements = [elements];
8065
    }
8066
8067
    for (var e = 0, len = elements.length; e < len; e++) {
8068
        q = elements[e].querySelectorAll(query);
8069
        if (q) {
8070
            for(var i = q.length; i--; ret.unshift(q[i]));
0 ignored issues
show
introduced by
The for loop does not have a body. Maybe you have misplaced a semicolon. If you do wish to have a loop without a body, use an empty body {}.
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8071
        }
8072
    }
8073
    return ret;
8074
};
8075
;wysihtml5.dom.compareDocumentPosition = (function() {
8076
  var documentElement = document.documentElement;
8077
  if (documentElement.compareDocumentPosition) {
8078
    return function(container, element) {
8079
      return container.compareDocumentPosition(element);
8080
    };
8081
  } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8082
    return function( container, element ) {
8083
      // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
8084
      var thisOwner, otherOwner;
8085
8086
      if( container.nodeType === 9) // Node.DOCUMENT_NODE
8087
        thisOwner = container;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8088
      else
8089
        thisOwner = container.ownerDocument;
8090
8091
      if( element.nodeType === 9) // Node.DOCUMENT_NODE
8092
        otherOwner = element;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8093
      else
8094
        otherOwner = element.ownerDocument;
8095
8096
      if( container === element ) return 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8097
      if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8098
      if( container.ownerDocument === element ) return 2 + 8;  //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8099
      if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8100
8101
      // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
8102
      if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml5.lang.array(container.childNodes).indexOf( element ) !== -1)
8103
        return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8104
8105
      if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml5.lang.array(element.childNodes).indexOf( container ) !== -1)
8106
        return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8107
8108
      var point = container;
8109
      var parents = [ ];
8110
      var previous = null;
0 ignored issues
show
Unused Code introduced by
The assignment to previous seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
8111
      while( point ) {
8112
        if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8113
        parents.push( point );
8114
        point = point.parentNode;
8115
      }
8116
      point = element;
8117
      previous = null;
8118
      while( point ) {
8119
        if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8120
        var location_index = wysihtml5.lang.array(parents).indexOf( point );
8121
        if( location_index !== -1) {
8122
         var smallest_common_ancestor = parents[ location_index ];
8123
         var this_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] );
8124
         var other_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
8125
         if( this_index > other_index ) {
8126
               return 2; //Node.DOCUMENT_POSITION_PRECEDING;
8127
         }
8128
         else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8129
           return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
8130
         }
8131
        }
8132
        previous = point;
8133
        point = point.parentNode;
8134
      }
8135
      return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
8136
    };
8137
  }
8138
})();
8139
;wysihtml5.dom.unwrap = function(node) {
8140
  if (node.parentNode) {
8141
    while (node.lastChild) {
8142
      wysihtml5.dom.insert(node.lastChild).after(node);
8143
    }
8144
    node.parentNode.removeChild(node);
8145
  }
8146
};;/**
8147
 * Fix most common html formatting misbehaviors of browsers implementation when inserting
8148
 * content via copy & paste contentEditable
8149
 *
8150
 * @author Christopher Blum
8151
 */
8152
wysihtml5.quirks.cleanPastedHTML = (function() {
8153
  // TODO: We probably need more rules here
8154
  var defaultRules = {
8155
    // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
8156
    "a u": wysihtml5.dom.replaceWithChildNodes
8157
  };
8158
8159
  function cleanPastedHTML(elementOrHtml, rules, context) {
8160
    rules   = rules || defaultRules;
8161
    context = context || elementOrHtml.ownerDocument || document;
8162
8163
    var element,
8164
        isString = typeof(elementOrHtml) === "string",
8165
        method,
8166
        matches,
8167
        matchesLength,
8168
        i,
8169
        j = 0, n;
8170
    if (isString) {
8171
      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
8172
    } else {
8173
      element = elementOrHtml;
8174
    }
8175
8176
    for (i in rules) {
8177
      matches       = element.querySelectorAll(i);
8178
      method        = rules[i];
8179
      matchesLength = matches.length;
8180
      for (; j<matchesLength; j++) {
8181
        method(matches[j]);
8182
      }
8183
    }
8184
8185
    // replace joined non-breakable spaces with unjoined
8186
    var txtnodes = wysihtml5.dom.getTextNodes(element);
8187
    for (n = txtnodes.length; n--;) {
8188
      txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
8189
    }
8190
8191
    matches = elementOrHtml = rules = null;
0 ignored issues
show
Unused Code introduced by
The assignment to variable matches seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The assignment to rules seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
Unused Code introduced by
The assignment to variable elementOrHtml seems to be never used. Consider removing it.
Loading history...
8192
8193
    return isString ? element.innerHTML : element;
8194
  }
8195
8196
  return cleanPastedHTML;
8197
})();
8198
;/**
8199
 * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
8200
 *
8201
 * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
8202
 * @exaple
8203
 *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
8204
 */
8205
wysihtml5.quirks.ensureProperClearing = (function() {
8206
  var clearIfNecessary = function() {
8207
    var element = this;
8208
    setTimeout(function() {
8209
      var innerHTML = element.innerHTML.toLowerCase();
8210
      if (innerHTML == "<p>&nbsp;</p>" ||
8211
          innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
8212
        element.innerHTML = "";
8213
      }
8214
    }, 0);
8215
  };
8216
8217
  return function(composer) {
8218
    wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
8219
  };
8220
})();
8221
;// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
8222
//
8223
// In Firefox this:
8224
//      var d = document.createElement("div");
8225
//      d.innerHTML ='<a href="~"></a>';
8226
//      d.innerHTML;
8227
// will result in:
8228
//      <a href="%7E"></a>
8229
// which is wrong
8230
(function(wysihtml5) {
8231
  var TILDE_ESCAPED = "%7E";
8232
  wysihtml5.quirks.getCorrectInnerHTML = function(element) {
8233
    var innerHTML = element.innerHTML;
8234
    if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
8235
      return innerHTML;
8236
    }
8237
8238
    var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
8239
        url,
8240
        urlToSearch,
8241
        length,
8242
        i;
8243
    for (i=0, length=elementsWithTilde.length; i<length; i++) {
8244
      url         = elementsWithTilde[i].href || elementsWithTilde[i].src;
8245
      urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
8246
      innerHTML   = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
8247
    }
8248
    return innerHTML;
8249
  };
8250
})(wysihtml5);
8251
;/**
8252
 * Force rerendering of a given element
8253
 * Needed to fix display misbehaviors of IE
8254
 *
8255
 * @param {Element} element The element object which needs to be rerendered
8256
 * @example
8257
 *    wysihtml5.quirks.redraw(document.body);
8258
 */
8259
(function(wysihtml5) {
8260
  var CLASS_NAME = "wysihtml5-quirks-redraw";
8261
8262
  wysihtml5.quirks.redraw = function(element) {
8263
    wysihtml5.dom.addClass(element, CLASS_NAME);
8264
    wysihtml5.dom.removeClass(element, CLASS_NAME);
8265
8266
    // Following hack is needed for firefox to make sure that image resize handles are properly removed
8267
    try {
8268
      var doc = element.ownerDocument;
8269
      doc.execCommand("italic", false, null);
8270
      doc.execCommand("italic", false, null);
8271
    } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
8272
  };
8273
})(wysihtml5);
8274
;wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
8275
8276
    var dom = wysihtml5.dom,
8277
        select = {
8278
            table: null,
8279
            start: null,
8280
            end: null,
8281
            cells: null,
8282
            select: selectCells
8283
        },
8284
        selection_class = "wysiwyg-tmp-selected-cell",
8285
        moveHandler = null,
8286
        upHandler = null;
8287
8288
    function init () {
8289
8290
        dom.observe(editable, "mousedown", function(event) {
8291
          var target = wysihtml5.dom.getParentElement(event.target, { nodeName: ["TD", "TH"] });
8292
          if (target) {
8293
              handleSelectionMousedown(target);
8294
          }
8295
        });
8296
8297
        return select;
8298
    }
8299
8300
    function handleSelectionMousedown (target) {
8301
      select.start = target;
8302
      select.end = target;
8303
      select.cells = [target];
8304
      select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8305
8306
      if (select.table) {
8307
        removeCellSelections();
8308
        dom.addClass(target, selection_class);
8309
        moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
8310
        upHandler = dom.observe(editable, "mouseup", handleMouseUp);
8311
        editor.fire("tableselectstart").fire("tableselectstart:composer");
8312
      }
8313
    }
8314
8315
    // remove all selection classes
8316
    function removeCellSelections () {
8317
        if (editable) {
8318
            var selectedCells = editable.querySelectorAll('.' + selection_class);
8319
            if (selectedCells.length > 0) {
8320
              for (var i = 0; i < selectedCells.length; i++) {
8321
                  dom.removeClass(selectedCells[i], selection_class);
8322
              }
8323
            }
8324
        }
8325
    }
8326
8327
    function addSelections (cells) {
8328
      for (var i = 0; i < cells.length; i++) {
8329
        dom.addClass(cells[i], selection_class);
8330
      }
8331
    }
8332
8333
    function handleMouseMove (event) {
8334
      var curTable = null,
0 ignored issues
show
Unused Code introduced by
The assignment to curTable seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
8335
          cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
8336
          oldEnd;
8337
8338
      if (cell && select.table && select.start) {
8339
        curTable =  dom.getParentElement(cell, { nodeName: ["TABLE"] });
8340
        if (curTable && curTable === select.table) {
8341
          removeCellSelections();
8342
          oldEnd = select.end;
8343
          select.end = cell;
8344
          select.cells = dom.table.getCellsBetween(select.start, cell);
8345
          if (select.cells.length > 1) {
8346
            editor.composer.selection.deselect();
8347
          }
8348
          addSelections(select.cells);
8349
          if (select.end !== oldEnd) {
8350
            editor.fire("tableselectchange").fire("tableselectchange:composer");
8351
          }
8352
        }
8353
      }
8354
    }
8355
8356
    function handleMouseUp (event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
8357
      moveHandler.stop();
8358
      upHandler.stop();
8359
      editor.fire("tableselect").fire("tableselect:composer");
8360
      setTimeout(function() {
8361
        bindSideclick();
8362
      },0);
8363
    }
8364
8365
    function bindSideclick () {
8366
        var sideClickHandler = dom.observe(editable.ownerDocument, "click", function(event) {
8367
          sideClickHandler.stop();
8368
          if (dom.getParentElement(event.target, { nodeName: ["TABLE"] }) != select.table) {
8369
              removeCellSelections();
8370
              select.table = null;
8371
              select.start = null;
8372
              select.end = null;
8373
              editor.fire("tableunselect").fire("tableunselect:composer");
8374
          }
8375
        });
8376
    }
8377
8378
    function selectCells (start, end) {
8379
        select.start = start;
8380
        select.end = end;
8381
        select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8382
        selectedCells = dom.table.getCellsBetween(select.start, select.end);
0 ignored issues
show
Bug introduced by
The variable selectedCells seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.selectedCells.
Loading history...
8383
        addSelections(selectedCells);
8384
        bindSideclick();
8385
        editor.fire("tableselect").fire("tableselect:composer");
8386
    }
8387
8388
    return init();
8389
8390
};
8391
;(function(wysihtml5) {
8392
  var RGBA_REGEX     = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
8393
      RGB_REGEX      = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
8394
      HEX6_REGEX     = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
8395
      HEX3_REGEX     = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
8396
8397
  var param_REGX = function (p) {
8398
    return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+" , "gi");
8399
  };
8400
8401
  wysihtml5.quirks.styleParser = {
8402
8403
    parseColor: function(stylesStr, paramName) {
8404
      var paramRegex = param_REGX(paramName),
8405
          params = stylesStr.match(paramRegex),
8406
          radix = 10,
8407
          str, colorMatch;
8408
8409
      if (params) {
8410
        for (var i = params.length; i--;) {
8411
          params[i] = wysihtml5.lang.string(params[i].split(':')[1]).trim();
8412
        }
8413
        str = params[params.length-1];
8414
8415
        if (RGBA_REGEX.test(str)) {
8416
          colorMatch = str.match(RGBA_REGEX);
8417
        } else if (RGB_REGEX.test(str)) {
8418
          colorMatch = str.match(RGB_REGEX);
8419
        } else if (HEX6_REGEX.test(str)) {
8420
          colorMatch = str.match(HEX6_REGEX);
8421
          radix = 16;
8422
        } else if (HEX3_REGEX.test(str)) {
8423
          colorMatch = str.match(HEX3_REGEX);
8424
          colorMatch.shift();
8425
          colorMatch.push(1);
8426
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8427
            return (idx < 3) ? (parseInt(d, 16) * 16) + parseInt(d, 16): parseFloat(d);
8428
          });
8429
        }
8430
8431
        if (colorMatch) {
8432
          colorMatch.shift();
8433
          if (!colorMatch[3]) {
8434
            colorMatch.push(1);
8435
          }
8436
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8437
            return (idx < 3) ? parseInt(d, radix): parseFloat(d);
8438
          });
8439
        }
8440
      }
8441
      return false;
8442
    },
8443
8444
    unparseColor: function(val, props) {
8445
      if (props) {
8446
        if (props == "hex") {
8447
          return (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8448
        } else if (props == "hash") {
8449
          return "#" + (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8450
        } else if (props == "rgb") {
8451
          return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8452
        } else if (props == "rgba") {
8453
          return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8454
        } else if (props == "csv") {
8455
          return  val[0] + "," + val[1] + "," + val[2] + "," + val[3];
8456
        }
8457
      }
8458
8459
      if (val[3] && val[3] !== 1) {
8460
        return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8461
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8462
        return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8463
      }
8464
    },
8465
8466
    parseFontSize: function(stylesStr) {
8467
      var params = stylesStr.match(param_REGX('font-size'));
8468
      if (params) {
8469
        return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
8470
      }
8471
      return false;
8472
    }
8473
  };
8474
8475
})(wysihtml5);
8476
;/**
8477
 * Selection API
8478
 *
8479
 * @example
8480
 *    var selection = new wysihtml5.Selection(editor);
8481
 */
8482
(function(wysihtml5) {
8483
  var dom = wysihtml5.dom;
8484
8485
  function _getCumulativeOffsetTop(element) {
8486
    var top = 0;
8487
    if (element.parentNode) {
8488
      do {
8489
        top += element.offsetTop || 0;
8490
        element = element.offsetParent;
8491
      } while (element);
8492
    }
8493
    return top;
8494
  }
8495
8496
  // Provides the depth of ``descendant`` relative to ``ancestor``
8497
  function getDepth(ancestor, descendant) {
8498
      var ret = 0;
8499
      while (descendant !== ancestor) {
8500
          ret++;
8501
          descendant = descendant.parentNode;
8502
          if (!descendant)
8503
              throw new Error("not a descendant of ancestor!");
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8504
      }
8505
      return ret;
8506
  }
8507
8508
  // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
8509
  // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
8510
  function expandRangeToSurround(range) {
8511
      if (range.canSurroundContents()) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8512
8513
      var common = range.commonAncestorContainer,
8514
          start_depth = getDepth(common, range.startContainer),
8515
          end_depth = getDepth(common, range.endContainer);
8516
8517
      while(!range.canSurroundContents()) {
8518
        // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth.
8519
        if (start_depth > end_depth) {
8520
            range.setStartBefore(range.startContainer);
8521
            start_depth = getDepth(common, range.startContainer);
8522
        }
8523
        else {
8524
            range.setEndAfter(range.endContainer);
8525
            end_depth = getDepth(common, range.endContainer);
8526
        }
8527
      }
8528
  }
8529
8530
  wysihtml5.Selection = Base.extend(
8531
    /** @scope wysihtml5.Selection.prototype */ {
8532
    constructor: function(editor, contain, unselectableClass) {
8533
      // Make sure that our external range library is initialized
8534
      window.rangy.init();
8535
8536
      this.editor   = editor;
8537
      this.composer = editor.composer;
8538
      this.doc      = this.composer.doc;
8539
      this.contain = contain;
8540
      this.unselectableClass = unselectableClass || false;
8541
    },
8542
8543
    /**
8544
     * Get the current selection as a bookmark to be able to later restore it
8545
     *
8546
     * @return {Object} An object that represents the current selection
8547
     */
8548
    getBookmark: function() {
8549
      var range = this.getRange();
8550
      if (range) expandRangeToSurround(range);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
8551
      return range && range.cloneRange();
8552
    },
8553
8554
    /**
8555
     * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
8556
     *
8557
     * @param {Object} bookmark An object that represents the current selection
8558
     */
8559
    setBookmark: function(bookmark) {
8560
      if (!bookmark) {
8561
        return;
8562
      }
8563
8564
      this.setSelection(bookmark);
8565
    },
8566
8567
    /**
8568
     * Set the caret in front of the given node
8569
     *
8570
     * @param {Object} node The element or text node where to position the caret in front of
8571
     * @example
8572
     *    selection.setBefore(myElement);
8573
     */
8574
    setBefore: function(node) {
8575
      var range = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8576
      range.setStartBefore(node);
8577
      range.setEndBefore(node);
8578
      return this.setSelection(range);
8579
    },
8580
8581
    /**
8582
     * Set the caret after the given node
8583
     *
8584
     * @param {Object} node The element or text node where to position the caret in front of
8585
     * @example
8586
     *    selection.setBefore(myElement);
8587
     */
8588
    setAfter: function(node) {
8589
      var range = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8590
8591
      range.setStartAfter(node);
8592
      range.setEndAfter(node);
8593
      return this.setSelection(range);
8594
    },
8595
8596
    /**
8597
     * Ability to select/mark nodes
8598
     *
8599
     * @param {Element} node The node/element to select
8600
     * @example
8601
     *    selection.selectNode(document.getElementById("my-image"));
8602
     */
8603
    selectNode: function(node, avoidInvisibleSpace) {
8604
      var range           = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8605
          isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
8606
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
8607
          content         = isElement ? node.innerHTML : node.data,
8608
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
8609
          displayStyle    = dom.getStyle("display").from(node),
8610
          isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
8611
8612
      if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
8613
        // Make sure that caret is visible in node by inserting a zero width no breaking space
8614
        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
8615
      }
8616
8617
      if (canHaveHTML) {
8618
        range.selectNodeContents(node);
8619
      } else {
8620
        range.selectNode(node);
8621
      }
8622
8623
      if (canHaveHTML && isEmpty && isElement) {
8624
        range.collapse(isBlockElement);
8625
      } else if (canHaveHTML && isEmpty) {
8626
        range.setStartAfter(node);
8627
        range.setEndAfter(node);
8628
      }
8629
8630
      this.setSelection(range);
8631
    },
8632
8633
    /**
8634
     * Get the node which contains the selection
8635
     *
8636
     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
8637
     * @return {Object} The node that contains the caret
8638
     * @example
8639
     *    var nodeThatContainsCaret = selection.getSelectedNode();
8640
     */
8641
    getSelectedNode: function(controlRange) {
8642
      var selection,
8643
          range;
8644
8645
      if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
8646
        range = this.doc.selection.createRange();
8647
        if (range && range.length) {
8648
          return range.item(0);
8649
        }
8650
      }
8651
8652
      selection = this.getSelection(this.doc);
8653
      if (selection.focusNode === selection.anchorNode) {
8654
        return selection.focusNode;
8655
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8656
        range = this.getRange(this.doc);
8657
        return range ? range.commonAncestorContainer : this.doc.body;
8658
      }
8659
    },
8660
8661
    fixSelBorders: function() {
8662
      var range = this.getRange();
8663
      expandRangeToSurround(range);
8664
      this.setSelection(range);
8665
    },
8666
8667
    getSelectedOwnNodes: function(controlRange) {
0 ignored issues
show
Unused Code introduced by
The parameter controlRange is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
8668
      var selection,
0 ignored issues
show
Unused Code introduced by
The variable selection seems to be never used. Consider removing it.
Loading history...
8669
          ranges = this.getOwnRanges(),
8670
          ownNodes = [];
8671
8672
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8673
          ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
8674
      }
8675
      return ownNodes;
8676
    },
8677
8678
    findNodesInSelection: function(nodeTypes) {
8679
      var ranges = this.getOwnRanges(),
8680
          nodes = [], curNodes;
8681
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8682
        curNodes = ranges[i].getNodes([1], function(node) {
8683
            return wysihtml5.lang.array(nodeTypes).contains(node.nodeName);
8684
        });
8685
        nodes = nodes.concat(curNodes);
8686
      }
8687
      return nodes;
8688
    },
8689
8690
    containsUneditable: function() {
8691
      var uneditables = this.getOwnUneditables(),
8692
          selection = this.getSelection();
8693
8694
      for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
8695
        if (selection.containsNode(uneditables[i])) {
8696
          return true;
8697
        }
8698
      }
8699
8700
      return false;
8701
    },
8702
8703
    deleteContents: function()  {
8704
      var ranges = this.getOwnRanges();
8705
      for (var i = ranges.length; i--;) {
8706
        ranges[i].deleteContents();
8707
      }
8708
      this.setSelection(ranges[0]);
8709
    },
8710
8711
    getPreviousNode: function(node, ignoreEmpty) {
8712
      if (!node) {
8713
        var selection = this.getSelection();
8714
        node = selection.anchorNode;
8715
      }
8716
8717
      if (node === this.contain) {
8718
          return false;
8719
      }
8720
8721
      var ret = node.previousSibling,
8722
          parent;
8723
8724
      if (ret === this.contain) {
8725
          return false;
8726
      }
8727
8728
      if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
8729
         // do not count comments and other node types
8730
         ret = this.getPreviousNode(ret, ignoreEmpty);
8731
      } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
8732
        // do not count empty textnodes as previus nodes
8733
        ret = this.getPreviousNode(ret, ignoreEmpty);
8734
      } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML)) {
8735
        // Do not count empty nodes if param set.
8736
        // Contenteditable tends to bypass and delete these silently when deleting with caret
8737
        ret = this.getPreviousNode(ret, ignoreEmpty);
8738
      } else if (!ret && node !== this.contain) {
8739
        parent = node.parentNode;
8740
        if (parent !== this.contain) {
8741
            ret = this.getPreviousNode(parent, ignoreEmpty);
8742
        }
8743
      }
8744
8745
      return (ret !== this.contain) ? ret : false;
8746
    },
8747
8748
    getSelectionParentsByTag: function(tagName) {
0 ignored issues
show
Unused Code introduced by
The parameter tagName is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
8749
      var nodes = this.getSelectedOwnNodes(),
8750
          curEl, parents = [];
8751
8752
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
8753
        curEl = (nodes[i].nodeName &&  nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml5.dom.getParentElement(nodes[i], { nodeName: ['LI']}, false, this.contain);
8754
        if (curEl) {
8755
          parents.push(curEl);
8756
        }
8757
      }
8758
      return (parents.length) ? parents : null;
8759
    },
8760
8761
    getRangeToNodeEnd: function() {
8762
      if (this.isCollapsed()) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if this.isCollapsed() is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
8763
        var range = this.getRange(),
8764
            sNode = range.startContainer,
8765
            pos = range.startOffset,
8766
            lastR = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8767
8768
        lastR.selectNodeContents(sNode);
8769
        lastR.setStart(sNode, pos);
8770
        return lastR;
8771
      }
8772
    },
8773
8774
    caretIsLastInSelection: function() {
8775
      var r = rangy.createRange(this.doc),
0 ignored issues
show
Unused Code introduced by
The variable r seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8776
          s = this.getSelection(),
0 ignored issues
show
Unused Code introduced by
The variable s seems to be never used. Consider removing it.
Loading history...
8777
          endc = this.getRangeToNodeEnd().cloneContents(),
8778
          endtxt = endc.textContent;
8779
8780
      return (/^\s*$/).test(endtxt);
8781
    },
8782
8783
    caretIsFirstInSelection: function() {
8784
      var r = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8785
          s = this.getSelection(),
8786
          range = this.getRange(),
8787
          startNode = range.startContainer;
8788
      
8789
      if (startNode.nodeType === wysihtml5.TEXT_NODE) {
8790
        return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
8791
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8792
        r.selectNodeContents(this.getRange().commonAncestorContainer);
8793
        r.collapse(true);
8794
        return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
8795
      }
8796
    },
8797
8798
    caretIsInTheBeginnig: function(ofNode) {
8799
        var selection = this.getSelection(),
8800
            node = selection.anchorNode,
8801
            offset = selection.anchorOffset;
8802
        if (ofNode) {
8803
          return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
8804
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
8805
          return (offset === 0 && !this.getPreviousNode(node, true));
8806
        }
8807
    },
8808
8809
    caretIsBeforeUneditable: function() {
8810
      var selection = this.getSelection(),
8811
          node = selection.anchorNode,
8812
          offset = selection.anchorOffset;
8813
8814
      if (offset === 0) {
8815
        var prevNode = this.getPreviousNode(node, true);
8816
        if (prevNode) {
8817
          var uneditables = this.getOwnUneditables();
8818
          for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
8819
            if (prevNode === uneditables[i]) {
8820
              return uneditables[i];
8821
            }
8822
          }
8823
        }
8824
      }
8825
      return false;
8826
    },
8827
8828
    // TODO: Figure out a method from following 3 that would work universally
8829
    executeAndRestoreRangy: function(method, restoreScrollPosition) {
0 ignored issues
show
Unused Code introduced by
The parameter restoreScrollPosition is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
8830
      var win = this.doc.defaultView || this.doc.parentWindow,
8831
          sel = rangy.saveSelection(win);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8832
8833
      if (!sel) {
8834
        method();
8835
      } else {
8836
        try {
8837
          method();
8838
        } catch(e) {
8839
          setTimeout(function() { throw e; }, 0);
8840
        }
8841
      }
8842
      rangy.restoreSelection(sel);
8843
    },
8844
8845
    // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
8846
    executeAndRestore: function(method, restoreScrollPosition) {
8847
      var body                  = this.doc.body,
8848
          oldScrollTop          = restoreScrollPosition && body.scrollTop,
8849
          oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
8850
          className             = "_wysihtml5-temp-placeholder",
8851
          placeholderHtml       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
8852
          range                 = this.getRange(true),
8853
          caretPlaceholder,
8854
          newCaretPlaceholder,
8855
          nextSibling, prevSibling,
8856
          node, node2, range2,
8857
          newRange;
8858
8859
      // Nothing selected, execute and say goodbye
8860
      if (!range) {
8861
        method(body, body);
8862
        return;
8863
      }
8864
8865
      if (!range.collapsed) {
8866
        range2 = range.cloneRange();
8867
        node2 = range2.createContextualFragment(placeholderHtml);
8868
        range2.collapse(false);
8869
        range2.insertNode(node2);
8870
        range2.detach();
8871
      }
8872
8873
      node = range.createContextualFragment(placeholderHtml);
8874
      range.insertNode(node);
8875
8876
      if (node2) {
8877
        caretPlaceholder = this.contain.querySelectorAll("." + className);
8878
        range.setStartBefore(caretPlaceholder[0]);
8879
        range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
8880
      }
8881
      this.setSelection(range);
8882
8883
      // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
8884
      try {
8885
        method(range.startContainer, range.endContainer);
8886
      } catch(e) {
8887
        setTimeout(function() { throw e; }, 0);
8888
      }
8889
      caretPlaceholder = this.contain.querySelectorAll("." + className);
8890
      if (caretPlaceholder && caretPlaceholder.length) {
8891
        newRange = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8892
        nextSibling = caretPlaceholder[0].nextSibling;
8893
        if (caretPlaceholder.length > 1) {
8894
          prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
8895
        }
8896
        if (prevSibling && nextSibling) {
8897
          newRange.setStartBefore(nextSibling);
8898
          newRange.setEndAfter(prevSibling);
8899
        } else {
8900
          newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
8901
          dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
8902
          newRange.setStartBefore(newCaretPlaceholder);
8903
          newRange.setEndAfter(newCaretPlaceholder);
8904
        }
8905
        this.setSelection(newRange);
8906
        for (var i = caretPlaceholder.length; i--;) {
8907
         caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
8908
        }
8909
8910
      } else {
8911
        // fallback for when all hell breaks loose
8912
        this.contain.focus();
8913
      }
8914
8915
      if (restoreScrollPosition) {
8916
        body.scrollTop  = oldScrollTop;
8917
        body.scrollLeft = oldScrollLeft;
8918
      }
8919
8920
      // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
8921
      try {
8922
        caretPlaceholder.parentNode.removeChild(caretPlaceholder);
8923
      } catch(e2) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
8924
    },
8925
8926
    set: function(node, offset) {
8927
      var newRange = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8928
      newRange.setStart(node, offset || 0);
8929
      this.setSelection(newRange);
8930
    },
8931
8932
    /**
8933
     * Insert html at the caret position and move the cursor after the inserted html
8934
     *
8935
     * @param {String} html HTML string to insert
8936
     * @example
8937
     *    selection.insertHTML("<p>foobar</p>");
8938
     */
8939
    insertHTML: function(html) {
8940
      var range     = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
8941
          node      = range.createContextualFragment(html),
8942
          lastChild = node.lastChild;
8943
8944
      this.insertNode(node);
8945
      if (lastChild) {
8946
        this.setAfter(lastChild);
8947
      }
8948
    },
8949
8950
    /**
8951
     * Insert a node at the caret position and move the cursor behind it
8952
     *
8953
     * @param {Object} node HTML string to insert
8954
     * @example
8955
     *    selection.insertNode(document.createTextNode("foobar"));
8956
     */
8957
    insertNode: function(node) {
8958
      var range = this.getRange();
8959
      if (range) {
8960
        range.insertNode(node);
8961
      }
8962
    },
8963
8964
    /**
8965
     * Wraps current selection with the given node
8966
     *
8967
     * @param {Object} node The node to surround the selected elements with
8968
     */
8969
    surround: function(nodeOptions) {
8970
      var ranges = this.getOwnRanges(),
8971
          node, nodes = [];
8972
      if (ranges.length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing ranges.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
8973
        return nodes;
8974
      }
8975
8976
      for (var i = ranges.length; i--;) {
8977
        node = this.doc.createElement(nodeOptions.nodeName);
8978
        nodes.push(node);
8979
        if (nodeOptions.className) {
8980
          node.className = nodeOptions.className;
8981
        }
8982
        if (nodeOptions.cssStyle) {
8983
          node.setAttribute('style', nodeOptions.cssStyle);
8984
        }
8985
        try {
8986
          // This only works when the range boundaries are not overlapping other elements
8987
          ranges[i].surroundContents(node);
8988
          this.selectNode(node);
8989
        } catch(e) {
8990
          // fallback
8991
          node.appendChild(ranges[i].extractContents());
8992
          ranges[i].insertNode(node);
8993
        }
8994
      }
8995
      return nodes;
8996
    },
8997
8998
    deblockAndSurround: function(nodeOptions) {
8999
      var tempElement = this.doc.createElement('div'),
9000
          range = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9001
          tempDivElements,
9002
          tempElements,
9003
          firstChild;
9004
9005
      tempElement.className = nodeOptions.className;
9006
9007
      this.composer.commands.exec("formatBlock", nodeOptions.nodeName, nodeOptions.className);
9008
      tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
9009
      if (tempDivElements[0]) {
9010
        tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
9011
9012
        range.setStartBefore(tempDivElements[0]);
9013
        range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
9014
        tempElements = range.extractContents();
9015
9016
        while (tempElements.firstChild) {
9017
          firstChild = tempElements.firstChild;
9018
          if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
0 ignored issues
show
Best Practice introduced by
Comparing firstChild.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
9019
            while (firstChild.firstChild) {
9020
              tempElement.appendChild(firstChild.firstChild);
9021
            }
9022
            if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
9023
            tempElements.removeChild(firstChild);
9024
          } else {
9025
            tempElement.appendChild(firstChild);
9026
          }
9027
        }
9028
      } else {
9029
        tempElement = null;
9030
      }
9031
9032
      return tempElement;
9033
    },
9034
9035
    /**
9036
     * Scroll the current caret position into the view
9037
     * FIXME: This is a bit hacky, there might be a smarter way of doing this
9038
     *
9039
     * @example
9040
     *    selection.scrollIntoView();
9041
     */
9042
    scrollIntoView: function() {
9043
      var doc           = this.doc,
9044
          tolerance     = 5, // px
9045
          hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
9046
          tempElement   = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
9047
            var element = doc.createElement("span");
9048
            // The element needs content in order to be able to calculate it's position properly
9049
            element.innerHTML = wysihtml5.INVISIBLE_SPACE;
9050
            return element;
9051
          })(),
9052
          offsetTop;
9053
9054
      if (hasScrollBars) {
9055
        this.insertNode(tempElement);
9056
        offsetTop = _getCumulativeOffsetTop(tempElement);
9057
        tempElement.parentNode.removeChild(tempElement);
9058
        if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
9059
          doc.body.scrollTop = offsetTop;
9060
        }
9061
      }
9062
    },
9063
9064
    /**
9065
     * Select line where the caret is in
9066
     */
9067
    selectLine: function() {
9068
      if (wysihtml5.browser.supportsSelectionModify()) {
9069
        this._selectLine_W3C();
9070
      } else if (this.doc.selection) {
9071
        this._selectLine_MSIE();
9072
      }
9073
    },
9074
9075
    /**
9076
     * See https://developer.mozilla.org/en/DOM/Selection/modify
9077
     */
9078
    _selectLine_W3C: function() {
9079
      var win       = this.doc.defaultView,
9080
          selection = win.getSelection();
9081
      selection.modify("move", "left", "lineboundary");
9082
      selection.modify("extend", "right", "lineboundary");
9083
    },
9084
9085
    _selectLine_MSIE: function() {
9086
      var range       = this.doc.selection.createRange(),
9087
          rangeTop    = range.boundingTop,
9088
          scrollWidth = this.doc.body.scrollWidth,
9089
          rangeBottom,
9090
          rangeEnd,
9091
          measureNode,
9092
          i,
9093
          j;
9094
9095
      if (!range.moveToPoint) {
9096
        return;
9097
      }
9098
9099
      if (rangeTop === 0) {
9100
        // Don't know why, but when the selection ends at the end of a line
9101
        // range.boundingTop is 0
9102
        measureNode = this.doc.createElement("span");
9103
        this.insertNode(measureNode);
9104
        rangeTop = measureNode.offsetTop;
9105
        measureNode.parentNode.removeChild(measureNode);
9106
      }
9107
9108
      rangeTop += 1;
9109
9110
      for (i=-10; i<scrollWidth; i+=2) {
9111
        try {
9112
          range.moveToPoint(i, rangeTop);
9113
          break;
9114
        } catch(e1) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
9115
      }
9116
9117
      // Investigate the following in order to handle multi line selections
9118
      // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
9119
      rangeBottom = rangeTop;
9120
      rangeEnd = this.doc.selection.createRange();
9121
      for (j=scrollWidth; j>=0; j--) {
9122
        try {
9123
          rangeEnd.moveToPoint(j, rangeBottom);
9124
          break;
9125
        } catch(e2) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
9126
      }
9127
9128
      range.setEndPoint("EndToEnd", rangeEnd);
9129
      range.select();
9130
    },
9131
9132
    getText: function() {
9133
      var selection = this.getSelection();
9134
      return selection ? selection.toString() : "";
9135
    },
9136
9137
    getNodes: function(nodeType, filter) {
9138
      var range = this.getRange();
9139
      if (range) {
9140
        return range.getNodes([nodeType], filter);
9141
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
9142
        return [];
9143
      }
9144
    },
9145
9146
    fixRangeOverflow: function(range) {
9147
      if (this.contain && this.contain.firstChild && range) {
9148
        var containment = range.compareNode(this.contain);
9149
        if (containment !== 2) {
9150
          if (containment === 1) {
9151
            range.setStartBefore(this.contain.firstChild);
9152
          }
9153
          if (containment === 0) {
9154
            range.setEndAfter(this.contain.lastChild);
9155
          }
9156
          if (containment === 3) {
9157
            range.setStartBefore(this.contain.firstChild);
9158
            range.setEndAfter(this.contain.lastChild);
9159
          }
9160
        } else if (this._detectInlineRangeProblems(range)) {
9161
          var previousElementSibling = range.endContainer.previousElementSibling;
9162
          if (previousElementSibling) {
9163
            range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
9164
          }
9165
        }
9166
      }
9167
    },
9168
9169
    _endOffsetForNode: function(node) {
9170
      var range = document.createRange();
9171
      range.selectNodeContents(node);
9172
      return range.endOffset;
9173
    },
9174
9175
    _detectInlineRangeProblems: function(range) {
9176
      var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
9177
      return (
9178
        range.endOffset == 0 &&
0 ignored issues
show
Best Practice introduced by
Comparing range.endOffset to 0 using the == operator is not safe. Consider using === instead.
Loading history...
9179
        position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
9180
      );
9181
    },
9182
9183
    getRange: function(dontFix) {
9184
      var selection = this.getSelection(),
9185
          range = selection && selection.rangeCount && selection.getRangeAt(0);
9186
9187
      if (dontFix !== true) {
9188
        this.fixRangeOverflow(range);
9189
      }
9190
9191
      return range;
9192
    },
9193
9194
    getOwnUneditables: function() {
9195
      var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
9196
          deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
9197
9198
      return wysihtml5.lang.array(allUneditables).without(deepUneditables);
9199
    },
9200
9201
    // Returns an array of ranges that belong only to this editable
9202
    // Needed as uneditable block in contenteditabel can split range into pieces
9203
    // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
9204
    getOwnRanges: function()  {
9205
      var ranges = [],
9206
          r = this.getRange(),
9207
          tmpRanges;
9208
9209
      if (r) { ranges.push(r); }
9210
9211
      if (this.unselectableClass && this.contain && r) {
9212
          var uneditables = this.getOwnUneditables(),
9213
              tmpRange;
9214
          if (uneditables.length > 0) {
9215
            for (var i = 0, imax = uneditables.length; i < imax; i++) {
9216
              tmpRanges = [];
9217
              for (var j = 0, jmax = ranges.length; j < jmax; j++) {
9218
                if (ranges[j]) {
9219
                  switch (ranges[j].compareNode(uneditables[i])) {
9220
                    case 2:
9221
                      // all selection inside uneditable. remove
9222
                    break;
9223
                    case 3:
9224
                      //section begins before and ends after uneditable. spilt
9225
                      tmpRange = ranges[j].cloneRange();
9226
                      tmpRange.setEndBefore(uneditables[i]);
9227
                      tmpRanges.push(tmpRange);
9228
9229
                      tmpRange = ranges[j].cloneRange();
9230
                      tmpRange.setStartAfter(uneditables[i]);
9231
                      tmpRanges.push(tmpRange);
9232
                    break;
9233
                    default:
9234
                      // in all other cases uneditable does not touch selection. dont modify
9235
                      tmpRanges.push(ranges[j]);
9236
                  }
9237
                }
9238
                ranges = tmpRanges;
9239
              }
9240
            }
9241
          }
9242
      }
9243
      return ranges;
9244
    },
9245
9246
    getSelection: function() {
9247
      return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9248
    },
9249
9250
    setSelection: function(range) {
9251
      var win       = this.doc.defaultView || this.doc.parentWindow,
9252
          selection = rangy.getSelection(win);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9253
      return selection.setSingleRange(range);
9254
    },
9255
9256
    createRange: function() {
9257
      return rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9258
    },
9259
9260
    isCollapsed: function() {
9261
        return this.getSelection().isCollapsed;
9262
    },
9263
9264
    isEndToEndInNode: function(nodeNames) {
9265
      var range = this.getRange(),
9266
          parentElement = range.commonAncestorContainer,
9267
          startNode = range.startContainer,
9268
          endNode = range.endContainer;
9269
9270
9271
        if (parentElement.nodeType === wysihtml5.TEXT_NODE) {
9272
          parentElement = parentElement.parentNode;
9273
        }
9274
9275
        if (startNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
9276
          return false;
9277
        }
9278
9279
        if (endNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
9280
          return false;
9281
        }
9282
9283
        while (startNode && startNode !== parentElement) {
9284
          if (startNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, startNode)) {
9285
            return false;
9286
          }
9287
          if (wysihtml5.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
9288
            return false;
9289
          }
9290
          startNode = startNode.parentNode;
9291
        }
9292
9293
        while (endNode && endNode !== parentElement) {
9294
          if (endNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, endNode)) {
9295
            return false;
9296
          }
9297
          if (wysihtml5.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
9298
            return false;
9299
          }
9300
          endNode = endNode.parentNode;
9301
        }
9302
9303
        return (wysihtml5.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
9304
    },
9305
9306
    deselect: function() {
9307
      var sel = this.getSelection();
9308
      sel && sel.removeAllRanges();
9309
    }
9310
  });
9311
9312
})(wysihtml5);
9313
;/**
9314
 * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
9315
 * http://code.google.com/p/rangy/
9316
 *
9317
 * changed in order to be able ...
9318
 *    - to use custom tags
9319
 *    - to detect and replace similar css classes via reg exp
9320
 */
9321
(function(wysihtml5, rangy) {
9322
  var defaultTagName = "span";
9323
9324
  var REG_EXP_WHITE_SPACE = /\s+/g;
9325
9326
  function hasClass(el, cssClass, regExp) {
9327
    if (!el.className) {
9328
      return false;
9329
    }
9330
9331
    var matchingClassNames = el.className.match(regExp) || [];
9332
    return matchingClassNames[matchingClassNames.length - 1] === cssClass;
9333
  }
9334
9335
  function hasStyleAttr(el, regExp) {
9336
    if (!el.getAttribute || !el.getAttribute('style')) {
9337
      return false;
9338
    }
9339
    var matchingStyles = el.getAttribute('style').match(regExp);
0 ignored issues
show
Unused Code introduced by
The variable matchingStyles seems to be never used. Consider removing it.
Loading history...
9340
    return  (el.getAttribute('style').match(regExp)) ? true : false;
9341
  }
9342
9343
  function addStyle(el, cssStyle, regExp) {
9344
    if (el.getAttribute('style')) {
9345
      removeStyle(el, regExp);
9346
      if (el.getAttribute('style') && !(/^\s*$/).test(el.getAttribute('style'))) {
9347
        el.setAttribute('style', cssStyle + ";" + el.getAttribute('style'));
9348
      } else {
9349
        el.setAttribute('style', cssStyle);
9350
      }
9351
    } else {
9352
      el.setAttribute('style', cssStyle);
9353
    }
9354
  }
9355
9356
  function addClass(el, cssClass, regExp) {
9357
    if (el.className) {
9358
      removeClass(el, regExp);
9359
      el.className += " " + cssClass;
9360
    } else {
9361
      el.className = cssClass;
9362
    }
9363
  }
9364
9365
  function removeClass(el, regExp) {
9366
    if (el.className) {
9367
      el.className = el.className.replace(regExp, "");
9368
    }
9369
  }
9370
9371
  function removeStyle(el, regExp) {
9372
    var s,
9373
        s2 = [];
9374
    if (el.getAttribute('style')) {
9375
      s = el.getAttribute('style').split(';');
9376
      for (var i = s.length; i--;) {
9377
        if (!s[i].match(regExp) && !(/^\s*$/).test(s[i])) {
9378
          s2.push(s[i]);
9379
        }
9380
      }
9381
      if (s2.length) {
9382
        el.setAttribute('style', s2.join(';'));
9383
      } else {
9384
        el.removeAttribute('style');
9385
      }
9386
    }
9387
  }
9388
9389
  function getMatchingStyleRegexp(el, style) {
9390
    var regexes = [],
9391
        sSplit = style.split(';'),
9392
        elStyle = el.getAttribute('style');
9393
9394
    if (elStyle) {
9395
      elStyle = elStyle.replace(/\s/gi, '').toLowerCase();
9396
      regexes.push(new RegExp("(^|\\s|;)" + style.replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi, "\\s?rgb\\($1,\\s?$2,\\s?$3\\)"), "gi"));
9397
9398
      for (var i = sSplit.length; i-- > 0;) {
9399
        if (!(/^\s*$/).test(sSplit[i])) {
9400
          regexes.push(new RegExp("(^|\\s|;)" + sSplit[i].replace(/\s/gi, '').replace(/([\(\)])/gi, "\\$1").toLowerCase().replace(";", ";?").replace(/rgb\\\((\d+),(\d+),(\d+)\\\)/gi, "\\s?rgb\\($1,\\s?$2,\\s?$3\\)"), "gi"));
9401
        }
9402
      }
9403
      for (var j = 0, jmax = regexes.length; j < jmax; j++) {
9404
        if (elStyle.match(regexes[j])) {
9405
          return regexes[j];
9406
        }
9407
      }
9408
    }
9409
9410
    return false;
9411
  }
9412
9413
  function isMatchingAllready(node, tags, style, className) {
9414
    if (style) {
9415
      return getMatchingStyleRegexp(node, style);
9416
    } else if (className) {
9417
      return wysihtml5.dom.hasClass(node, className);
9418
    } else {
9419
      return rangy.dom.arrayContains(tags, node.tagName.toLowerCase());
9420
    }
9421
  }
9422
9423
  function areMatchingAllready(nodes, tags, style, className) {
9424
    for (var i = nodes.length; i--;) {
9425
      if (!isMatchingAllready(nodes[i], tags, style, className)) {
9426
        return false;
9427
      }
9428
    }
9429
    return nodes.length ? true : false;
9430
  }
9431
9432
  function removeOrChangeStyle(el, style, regExp) {
9433
9434
    var exactRegex = getMatchingStyleRegexp(el, style);
9435
    if (exactRegex) {
9436
      // adding same style value on property again removes style
9437
      removeStyle(el, exactRegex);
9438
      return "remove";
9439
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
9440
      // adding new style value changes value
9441
      addStyle(el, style, regExp);
9442
      return "change";
9443
    }
9444
  }
9445
9446
  function hasSameClasses(el1, el2) {
9447
    return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " ");
9448
  }
9449
9450
  function replaceWithOwnChildren(el) {
9451
    var parent = el.parentNode;
9452
    while (el.firstChild) {
9453
      parent.insertBefore(el.firstChild, el);
9454
    }
9455
    parent.removeChild(el);
9456
  }
9457
9458
  function elementsHaveSameNonClassAttributes(el1, el2) {
9459
    if (el1.attributes.length != el2.attributes.length) {
9460
      return false;
9461
    }
9462
    for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
9463
      attr1 = el1.attributes[i];
9464
      name = attr1.name;
9465
      if (name != "class") {
9466
        attr2 = el2.attributes.getNamedItem(name);
9467
        if (attr1.specified != attr2.specified) {
9468
          return false;
9469
        }
9470
        if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
9471
          return false;
9472
        }
9473
      }
9474
    }
9475
    return true;
9476
  }
9477
9478
  function isSplitPoint(node, offset) {
9479
    if (rangy.dom.isCharacterDataNode(node)) {
9480
      if (offset == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing offset to 0 using the == operator is not safe. Consider using === instead.
Loading history...
9481
        return !!node.previousSibling;
9482
      } else if (offset == node.length) {
9483
        return !!node.nextSibling;
9484
      } else {
9485
        return true;
9486
      }
9487
    }
9488
9489
    return offset > 0 && offset < node.childNodes.length;
9490
  }
9491
9492
  function splitNodeAt(node, descendantNode, descendantOffset, container) {
9493
    var newNode;
9494
    if (rangy.dom.isCharacterDataNode(descendantNode)) {
9495
      if (descendantOffset == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing descendantOffset to 0 using the == operator is not safe. Consider using === instead.
Loading history...
9496
        descendantOffset = rangy.dom.getNodeIndex(descendantNode);
9497
        descendantNode = descendantNode.parentNode;
9498
      } else if (descendantOffset == descendantNode.length) {
9499
        descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
9500
        descendantNode = descendantNode.parentNode;
9501
      } else {
9502
        newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
9503
      }
9504
    }
9505
    if (!newNode) {
9506
      if (!container || descendantNode !== container) {
9507
9508
        newNode = descendantNode.cloneNode(false);
9509
        if (newNode.id) {
9510
          newNode.removeAttribute("id");
9511
        }
9512
        var child;
9513
        while ((child = descendantNode.childNodes[descendantOffset])) {
9514
          newNode.appendChild(child);
9515
        }
9516
        rangy.dom.insertAfter(newNode, descendantNode);
9517
9518
      }
9519
    }
9520
    return (descendantNode == node) ? newNode :  splitNodeAt(node, newNode.parentNode, rangy.dom.getNodeIndex(newNode), container);
0 ignored issues
show
Bug introduced by
The variable newNode seems to not be initialized for all possible execution paths.
Loading history...
9521
  }
9522
9523
  function Merge(firstNode) {
9524
    this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
9525
    this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
9526
    this.textNodes = [this.firstTextNode];
9527
  }
9528
9529
  Merge.prototype = {
9530
    doMerge: function() {
9531
      var textBits = [], textNode, parent, text;
9532
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9533
        textNode = this.textNodes[i];
9534
        parent = textNode.parentNode;
9535
        textBits[i] = textNode.data;
9536
        if (i) {
9537
          parent.removeChild(textNode);
9538
          if (!parent.hasChildNodes()) {
9539
            parent.parentNode.removeChild(parent);
9540
          }
9541
        }
9542
      }
9543
      this.firstTextNode.data = text = textBits.join("");
9544
      return text;
9545
    },
9546
9547
    getLength: function() {
9548
      var i = this.textNodes.length, len = 0;
9549
      while (i--) {
9550
        len += this.textNodes[i].length;
9551
      }
9552
      return len;
9553
    },
9554
9555
    toString: function() {
9556
      var textBits = [];
9557
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9558
        textBits[i] = "'" + this.textNodes[i].data + "'";
9559
      }
9560
      return "[Merge(" + textBits.join(",") + ")]";
9561
    }
9562
  };
9563
9564
  function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize, cssStyle, similarStyleRegExp, container) {
9565
    this.tagNames = tagNames || [defaultTagName];
9566
    this.cssClass = cssClass || ((cssClass === false) ? false : "");
9567
    this.similarClassRegExp = similarClassRegExp;
9568
    this.cssStyle = cssStyle || "";
9569
    this.similarStyleRegExp = similarStyleRegExp;
9570
    this.normalize = normalize;
9571
    this.applyToAnyTagName = false;
9572
    this.container = container;
9573
  }
9574
9575
  HTMLApplier.prototype = {
9576
    getAncestorWithClass: function(node) {
9577
      var cssClassMatch;
9578
      while (node) {
9579
        cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : (this.cssStyle !== "") ? false : true;
9580
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" &&  rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
9581
          return node;
9582
        }
9583
        node = node.parentNode;
9584
      }
9585
      return false;
9586
    },
9587
9588
    // returns parents of node with given style attribute
9589
    getAncestorWithStyle: function(node) {
9590
      var cssStyleMatch;
9591
      while (node) {
9592
        cssStyleMatch = this.cssStyle ? hasStyleAttr(node, this.similarStyleRegExp) : false;
9593
9594
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssStyleMatch) {
9595
          return node;
9596
        }
9597
        node = node.parentNode;
9598
      }
9599
      return false;
9600
    },
9601
9602
    getMatchingAncestor: function(node) {
9603
      var ancestor = this.getAncestorWithClass(node),
9604
          matchType = false;
9605
9606
      if (!ancestor) {
9607
        ancestor = this.getAncestorWithStyle(node);
9608
        if (ancestor) {
9609
          matchType = "style";
9610
        }
9611
      } else {
9612
        if (this.cssStyle) {
9613
          matchType = "class";
9614
        }
9615
      }
9616
9617
      return {
9618
        "element": ancestor,
9619
        "type": matchType
9620
      };
9621
    },
9622
9623
    // Normalizes nodes after applying a CSS class to a Range.
9624
    postApply: function(textNodes, range) {
9625
      var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
9626
9627
      var merges = [], currentMerge;
9628
9629
      var rangeStartNode = firstNode, rangeEndNode = lastNode;
9630
      var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
9631
9632
      var textNode, precedingTextNode;
9633
9634
      for (var i = 0, len = textNodes.length; i < len; ++i) {
9635
        textNode = textNodes[i];
9636
        precedingTextNode = null;
9637
        if (textNode && textNode.parentNode) {
9638
          precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false);
9639
        }
9640
        if (precedingTextNode) {
9641
          if (!currentMerge) {
9642
            currentMerge = new Merge(precedingTextNode);
9643
            merges.push(currentMerge);
9644
          }
9645
          currentMerge.textNodes.push(textNode);
9646
          if (textNode === firstNode) {
9647
            rangeStartNode = currentMerge.firstTextNode;
9648
            rangeStartOffset = rangeStartNode.length;
9649
          }
9650
          if (textNode === lastNode) {
9651
            rangeEndNode = currentMerge.firstTextNode;
9652
            rangeEndOffset = currentMerge.getLength();
9653
          }
9654
        } else {
9655
          currentMerge = null;
9656
        }
9657
      }
9658
      // Test whether the first node after the range needs merging
9659
      if(lastNode && lastNode.parentNode) {
9660
        var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true);
9661
        if (nextTextNode) {
9662
          if (!currentMerge) {
9663
            currentMerge = new Merge(lastNode);
9664
            merges.push(currentMerge);
9665
          }
9666
          currentMerge.textNodes.push(nextTextNode);
9667
        }
9668
      }
9669
      // Do the merges
9670
      if (merges.length) {
9671
        for (i = 0, len = merges.length; i < len; ++i) {
9672
          merges[i].doMerge();
9673
        }
9674
        // Set the range boundaries
9675
        range.setStart(rangeStartNode, rangeStartOffset);
9676
        range.setEnd(rangeEndNode, rangeEndOffset);
9677
      }
9678
    },
9679
9680
    getAdjacentMergeableTextNode: function(node, forward) {
9681
        var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
9682
        var el = isTextNode ? node.parentNode : node;
9683
        var adjacentNode;
9684
        var propName = forward ? "nextSibling" : "previousSibling";
9685
        if (isTextNode) {
9686
          // Can merge if the node's previous/next sibling is a text node
9687
          adjacentNode = node[propName];
9688
          if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
9689
            return adjacentNode;
9690
          }
9691
        } else {
9692
          // Compare element with its sibling
9693
          adjacentNode = el[propName];
9694
          if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) {
9695
            return adjacentNode[forward ? "firstChild" : "lastChild"];
9696
          }
9697
        }
9698
        return null;
9699
    },
9700
9701
    areElementsMergeable: function(el1, el2) {
9702
      return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
9703
        && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
9704
        && hasSameClasses(el1, el2)
9705
        && elementsHaveSameNonClassAttributes(el1, el2);
9706
    },
9707
9708
    createContainer: function(doc) {
9709
      var el = doc.createElement(this.tagNames[0]);
9710
      if (this.cssClass) {
9711
        el.className = this.cssClass;
9712
      }
9713
      if (this.cssStyle) {
9714
        el.setAttribute('style', this.cssStyle);
9715
      }
9716
      return el;
9717
    },
9718
9719
    applyToTextNode: function(textNode) {
9720
      var parent = textNode.parentNode;
9721
      if (parent.childNodes.length == 1 && rangy.dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
0 ignored issues
show
Best Practice introduced by
Comparing parent.childNodes.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
9722
9723
        if (this.cssClass) {
9724
          addClass(parent, this.cssClass, this.similarClassRegExp);
9725
        }
9726
        if (this.cssStyle) {
9727
          addStyle(parent, this.cssStyle, this.similarStyleRegExp);
9728
        }
9729
      } else {
9730
        var el = this.createContainer(rangy.dom.getDocument(textNode));
9731
        textNode.parentNode.insertBefore(el, textNode);
9732
        el.appendChild(textNode);
9733
      }
9734
    },
9735
9736
    isRemovable: function(el) {
9737
      return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) &&
9738
              wysihtml5.lang.string(el.className).trim() === "" &&
9739
              (
9740
                !el.getAttribute('style') ||
9741
                wysihtml5.lang.string(el.getAttribute('style')).trim() === ""
9742
              );
9743
    },
9744
9745
    undoToTextNode: function(textNode, range, ancestorWithClass, ancestorWithStyle) {
9746
      var styleMode = (ancestorWithClass) ? false : true,
9747
          ancestor = ancestorWithClass || ancestorWithStyle,
9748
          styleChanged = false;
9749
      if (!range.containsNode(ancestor)) {
9750
        // Split out the portion of the ancestor from which we can remove the CSS class
9751
        var ancestorRange = range.cloneRange();
9752
            ancestorRange.selectNode(ancestor);
9753
9754
        if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) {
9755
            splitNodeAt(ancestor, range.endContainer, range.endOffset, this.container);
9756
            range.setEndAfter(ancestor);
9757
        }
9758
        if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
9759
            ancestor = splitNodeAt(ancestor, range.startContainer, range.startOffset, this.container);
9760
        }
9761
      }
9762
9763
      if (!styleMode && this.similarClassRegExp) {
9764
        removeClass(ancestor, this.similarClassRegExp);
9765
      }
9766
9767
      if (styleMode && this.similarStyleRegExp) {
9768
        styleChanged = (removeOrChangeStyle(ancestor, this.cssStyle, this.similarStyleRegExp) === "change");
9769
      }
9770
      if (this.isRemovable(ancestor) && !styleChanged) {
9771
        replaceWithOwnChildren(ancestor);
9772
      }
9773
    },
9774
9775
    applyToRange: function(range) {
9776
        var textNodes;
9777
        for (var ri = range.length; ri--;) {
9778
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9779
9780
            if (!textNodes.length) {
9781
              try {
9782
                var node = this.createContainer(range[ri].endContainer.ownerDocument);
9783
                range[ri].surroundContents(node);
9784
                this.selectNode(range[ri], node);
9785
                return;
9786
              } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
9787
            }
9788
9789
            range[ri].splitBoundaries();
9790
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9791
            if (textNodes.length) {
9792
              var textNode;
9793
9794
              for (var i = 0, len = textNodes.length; i < len; ++i) {
9795
                textNode = textNodes[i];
9796
                if (!this.getMatchingAncestor(textNode).element) {
9797
                  this.applyToTextNode(textNode);
9798
                }
9799
              }
9800
9801
              range[ri].setStart(textNodes[0], 0);
9802
              textNode = textNodes[textNodes.length - 1];
9803
              range[ri].setEnd(textNode, textNode.length);
9804
9805
              if (this.normalize) {
9806
                this.postApply(textNodes, range[ri]);
9807
              }
9808
            }
9809
9810
        }
9811
    },
9812
9813
    undoToRange: function(range) {
9814
      var textNodes, textNode, ancestorWithClass, ancestorWithStyle, ancestor;
0 ignored issues
show
Unused Code introduced by
The variable ancestorWithStyle seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable ancestorWithClass seems to be never used. Consider removing it.
Loading history...
9815
      for (var ri = range.length; ri--;) {
9816
9817
          textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9818
          if (textNodes.length) {
9819
            range[ri].splitBoundaries();
9820
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9821
          } else {
9822
            var doc = range[ri].endContainer.ownerDocument,
9823
                node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
9824
            range[ri].insertNode(node);
9825
            range[ri].selectNode(node);
9826
            textNodes = [node];
9827
          }
9828
9829
          for (var i = 0, len = textNodes.length; i < len; ++i) {
9830
            if (range[ri].isValid()) {
9831
              textNode = textNodes[i];
9832
9833
              ancestor = this.getMatchingAncestor(textNode);
9834
              if (ancestor.type === "style") {
9835
                this.undoToTextNode(textNode, range[ri], false, ancestor.element);
9836
              } else if (ancestor.element) {
9837
                this.undoToTextNode(textNode, range[ri], ancestor.element);
9838
              }
9839
            }
9840
          }
9841
9842
          if (len == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing len to 1 using the == operator is not safe. Consider using === instead.
Loading history...
9843
            this.selectNode(range[ri], textNodes[0]);
9844
          } else {
9845
            range[ri].setStart(textNodes[0], 0);
9846
            textNode = textNodes[textNodes.length - 1];
9847
            range[ri].setEnd(textNode, textNode.length);
9848
9849
            if (this.normalize) {
9850
              this.postApply(textNodes, range[ri]);
9851
            }
9852
          }
9853
9854
      }
9855
    },
9856
9857
    selectNode: function(range, node) {
9858
      var isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
9859
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : true,
9860
          content         = isElement ? node.innerHTML : node.data,
9861
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE);
9862
9863
      if (isEmpty && isElement && canHaveHTML) {
9864
        // Make sure that caret is visible in node by inserting a zero width no breaking space
9865
        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
9866
      }
9867
      range.selectNodeContents(node);
9868
      if (isEmpty && isElement) {
9869
        range.collapse(false);
9870
      } else if (isEmpty) {
9871
        range.setStartAfter(node);
9872
        range.setEndAfter(node);
9873
      }
9874
    },
9875
9876
    getTextSelectedByRange: function(textNode, range) {
9877
      var textRange = range.cloneRange();
9878
      textRange.selectNodeContents(textNode);
9879
9880
      var intersectionRange = textRange.intersection(range);
9881
      var text = intersectionRange ? intersectionRange.toString() : "";
9882
      textRange.detach();
9883
9884
      return text;
9885
    },
9886
9887
    isAppliedToRange: function(range) {
9888
      var ancestors = [],
9889
          appliedType = "full",
9890
          ancestor, styleAncestor, textNodes;
0 ignored issues
show
Unused Code introduced by
The variable styleAncestor seems to be never used. Consider removing it.
Loading history...
9891
9892
      for (var ri = range.length; ri--;) {
9893
9894
        textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9895
        if (!textNodes.length) {
9896
          ancestor = this.getMatchingAncestor(range[ri].startContainer).element;
9897
9898
          return (ancestor) ? {
9899
            "elements": [ancestor],
9900
            "coverage": appliedType
9901
          } : false;
9902
        }
9903
9904
        for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
9905
          selectedText = this.getTextSelectedByRange(textNodes[i], range[ri]);
9906
          ancestor = this.getMatchingAncestor(textNodes[i]).element;
9907
          if (ancestor && selectedText != "") {
9908
            ancestors.push(ancestor);
9909
9910
            if (wysihtml5.dom.getTextNodes(ancestor, true).length === 1) {
9911
              appliedType = "full";
9912
            } else if (appliedType === "full") {
9913
              appliedType = "inline";
9914
            }
9915
          } else if (!ancestor) {
9916
            appliedType = "partial";
9917
          }
9918
        }
9919
9920
      }
9921
9922
      return (ancestors.length) ? {
9923
        "elements": ancestors,
9924
        "coverage": appliedType
9925
      } : false;
9926
    },
9927
9928
    toggleRange: function(range) {
9929
      var isApplied = this.isAppliedToRange(range),
9930
          parentsExactMatch;
9931
9932
      if (isApplied) {
9933
        if (isApplied.coverage === "full") {
9934
          this.undoToRange(range);
9935
        } else if (isApplied.coverage === "inline") {
9936
          parentsExactMatch = areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass);
9937
          this.undoToRange(range);
9938
          if (!parentsExactMatch) {
9939
            this.applyToRange(range);
9940
          }
9941
        } else {
9942
          // partial
9943
          if (!areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass)) {
9944
            this.undoToRange(range);
9945
          }
9946
          this.applyToRange(range);
9947
        }
9948
      } else {
9949
        this.applyToRange(range);
9950
      }
9951
    }
9952
  };
9953
9954
  wysihtml5.selection.HTMLApplier = HTMLApplier;
9955
9956
})(wysihtml5, rangy);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
9957
;/**
9958
 * Rich Text Query/Formatting Commands
9959
 *
9960
 * @example
9961
 *    var commands = new wysihtml5.Commands(editor);
9962
 */
9963
wysihtml5.Commands = Base.extend(
9964
  /** @scope wysihtml5.Commands.prototype */ {
9965
  constructor: function(editor) {
9966
    this.editor   = editor;
9967
    this.composer = editor.composer;
9968
    this.doc      = this.composer.doc;
9969
  },
9970
9971
  /**
9972
   * Check whether the browser supports the given command
9973
   *
9974
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
9975
   * @example
9976
   *    commands.supports("createLink");
9977
   */
9978
  support: function(command) {
9979
    return wysihtml5.browser.supportsCommand(this.doc, command);
9980
  },
9981
9982
  /**
9983
   * Check whether the browser supports the given command
9984
   *
9985
   * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
9986
   * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
9987
   * @example
9988
   *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
9989
   */
9990
  exec: function(command, value) {
9991
    var obj     = wysihtml5.commands[command],
9992
        args    = wysihtml5.lang.array(arguments).get(),
9993
        method  = obj && obj.exec,
9994
        result  = null;
0 ignored issues
show
Unused Code introduced by
The assignment to result seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
9995
9996
    this.editor.fire("beforecommand:composer");
9997
9998
    if (method) {
9999
      args.unshift(this.composer);
10000
      result = method.apply(obj, args);
10001
    } else {
10002
      try {
10003
        // try/catch for buggy firefox
10004
        result = this.doc.execCommand(command, false, value);
10005
      } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
10006
    }
10007
10008
    this.editor.fire("aftercommand:composer");
10009
    return result;
10010
  },
10011
10012
  /**
10013
   * Check whether the current command is active
10014
   * If the caret is within a bold text, then calling this with command "bold" should return true
10015
   *
10016
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
10017
   * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
10018
   * @return {Boolean} Whether the command is active
10019
   * @example
10020
   *    var isCurrentSelectionBold = commands.state("bold");
10021
   */
10022
  state: function(command, commandValue) {
0 ignored issues
show
Unused Code introduced by
The parameter commandValue is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
10023
    var obj     = wysihtml5.commands[command],
10024
        args    = wysihtml5.lang.array(arguments).get(),
10025
        method  = obj && obj.state;
10026
    if (method) {
10027
      args.unshift(this.composer);
10028
      return method.apply(obj, args);
10029
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
10030
      try {
10031
        // try/catch for buggy firefox
10032
        return this.doc.queryCommandState(command);
10033
      } catch(e) {
10034
        return false;
10035
      }
10036
    }
10037
  },
10038
10039
  /* Get command state parsed value if command has stateValue parsing function */
10040
  stateValue: function(command) {
10041
    var obj     = wysihtml5.commands[command],
10042
        args    = wysihtml5.lang.array(arguments).get(),
10043
        method  = obj && obj.stateValue;
10044
    if (method) {
10045
      args.unshift(this.composer);
10046
      return method.apply(obj, args);
10047
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
10048
      return false;
10049
    }
10050
  }
10051
});
10052
;wysihtml5.commands.bold = {
10053
  exec: function(composer, command) {
10054
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "b");
10055
  },
10056
10057
  state: function(composer, command) {
10058
    // element.ownerDocument.queryCommandState("bold") results:
10059
    // firefox: only <b>
10060
    // chrome:  <b>, <strong>, <h1>, <h2>, ...
10061
    // ie:      <b>, <strong>
10062
    // opera:   <b>, <strong>
10063
    return wysihtml5.commands.formatInline.state(composer, command, "b");
10064
  }
10065
};
10066
10067
;(function(wysihtml5) {
10068
  var undef,
10069
      NODE_NAME = "A",
10070
      dom       = wysihtml5.dom;
10071
10072
  function _format(composer, attributes) {
10073
    var doc             = composer.doc,
10074
        tempClass       = "_wysihtml5-temp-" + (+new Date()),
10075
        tempClassRegExp = /non-matching-class/g,
10076
        i               = 0,
10077
        length,
10078
        anchors,
10079
        anchor,
10080
        hasElementChild,
10081
        isEmpty,
10082
        elementToSetCaretAfter,
10083
        textContent,
10084
        whiteSpace,
10085
        j;
10086
    wysihtml5.commands.formatInline.exec(composer, undef, NODE_NAME, tempClass, tempClassRegExp, undef, undef, true, true);
0 ignored issues
show
Bug introduced by
The variable undef seems to be never initialized.
Loading history...
10087
    anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
10088
    length  = anchors.length;
10089
    for (; i<length; i++) {
10090
      anchor = anchors[i];
10091
      anchor.removeAttribute("class");
10092
      for (j in attributes) {
10093
        // Do not set attribute "text" as it is meant for setting string value if created link has no textual data
10094
        if (j !== "text") {
10095
          anchor.setAttribute(j, attributes[j]);
10096
        }
10097
      }
10098
    }
10099
10100
    elementToSetCaretAfter = anchor;
0 ignored issues
show
Bug introduced by
The variable anchor seems to not be initialized for all possible execution paths.
Loading history...
10101
    if (length === 1) {
10102
      textContent = dom.getTextContent(anchor);
10103
      hasElementChild = !!anchor.querySelector("*");
10104
      isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
10105
      if (!hasElementChild && isEmpty) {
10106
        dom.setTextContent(anchor, attributes.text || anchor.href);
10107
        whiteSpace = doc.createTextNode(" ");
10108
        composer.selection.setAfter(anchor);
10109
        dom.insert(whiteSpace).after(anchor);
10110
        elementToSetCaretAfter = whiteSpace;
10111
      }
10112
    }
10113
    composer.selection.setAfter(elementToSetCaretAfter);
10114
  }
10115
10116
  // Changes attributes of links
10117
  function _changeLinks(composer, anchors, attributes) {
10118
    var oldAttrs;
10119
    for (var a = anchors.length; a--;) {
10120
10121
      // Remove all old attributes
10122
      oldAttrs = anchors[a].attributes;
10123
      for (var oa = oldAttrs.length; oa--;) {
10124
        anchors[a].removeAttribute(oldAttrs.item(oa).name);
10125
      }
10126
10127
      // Set new attributes
10128
      for (var j in attributes) {
10129
        if (attributes.hasOwnProperty(j)) {
10130
          anchors[a].setAttribute(j, attributes[j]);
10131
        }
10132
      }
10133
10134
    }
10135
  }
10136
10137
  wysihtml5.commands.createLink = {
10138
    /**
10139
     * TODO: Use HTMLApplier or formatInline here
10140
     *
10141
     * Turns selection into a link
10142
     * If selection is already a link, it just changes the attributes
10143
     *
10144
     * @example
10145
     *    // either ...
10146
     *    wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
10147
     *    // ... or ...
10148
     *    wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
10149
     */
10150
    exec: function(composer, command, value) {
10151
      var anchors = this.state(composer, command);
10152
      if (anchors) {
10153
        // Selection contains links then change attributes of these links
10154
        composer.selection.executeAndRestore(function() {
10155
          _changeLinks(composer, anchors, value);
10156
        });
10157
      } else {
10158
        // Create links
10159
        value = typeof(value) === "object" ? value : { href: value };
10160
        _format(composer, value);
10161
      }
10162
    },
10163
10164
    state: function(composer, command) {
10165
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10166
    }
10167
  };
10168
})(wysihtml5);
10169
;(function(wysihtml5) {
10170
  var dom = wysihtml5.dom;
10171
10172
  function _removeFormat(composer, anchors) {
10173
    var length  = anchors.length,
10174
        i       = 0,
10175
        anchor,
10176
        codeElement,
10177
        textContent;
10178
    for (; i<length; i++) {
10179
      anchor      = anchors[i];
10180
      codeElement = dom.getParentElement(anchor, { nodeName: "code" });
10181
      textContent = dom.getTextContent(anchor);
10182
10183
      // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking
10184
      // else replace <a> with its childNodes
10185
      if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) {
10186
        // <code> element is used to prevent later auto-linking of the content
10187
        codeElement = dom.renameElement(anchor, "code");
0 ignored issues
show
Unused Code introduced by
The assignment to variable codeElement seems to be never used. Consider removing it.
Loading history...
10188
      } else {
10189
        dom.replaceWithChildNodes(anchor);
10190
      }
10191
    }
10192
  }
10193
10194
  wysihtml5.commands.removeLink = {
10195
    /*
10196
     * If selection is a link, it removes the link and wraps it with a <code> element
10197
     * The <code> element is needed to avoid auto linking
10198
     *
10199
     * @example
10200
     *    wysihtml5.commands.createLink.exec(composer, "removeLink");
10201
     */
10202
10203
    exec: function(composer, command) {
10204
      var anchors = this.state(composer, command);
10205
      if (anchors) {
10206
        composer.selection.executeAndRestore(function() {
10207
          _removeFormat(composer, anchors);
10208
        });
10209
      }
10210
    },
10211
10212
    state: function(composer, command) {
10213
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10214
    }
10215
  };
10216
})(wysihtml5);
10217
;/**
10218
 * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
10219
 * which we don't want
10220
 * Instead we set a css class
10221
 */
10222
(function(wysihtml5) {
10223
  var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
10224
10225
  wysihtml5.commands.fontSize = {
10226
    exec: function(composer, command, size) {
10227
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10228
    },
10229
10230
    state: function(composer, command, size) {
10231
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10232
    }
10233
  };
10234
})(wysihtml5);
10235
;/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
10236
(function(wysihtml5) {
10237
  var REG_EXP = /(\s|^)font-size\s*:\s*[^;\s]+;?/gi;
10238
10239
  wysihtml5.commands.fontSizeStyle = {
10240
    exec: function(composer, command, size) {
10241
      size = (typeof(size) == "object") ? size.size : size;
10242
      if (!(/^\s*$/).test(size)) {
10243
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, "font-size:" + size, REG_EXP);
10244
      }
10245
    },
10246
10247
    state: function(composer, command, size) {
0 ignored issues
show
Unused Code introduced by
The parameter size is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
10248
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "font-size", REG_EXP);
10249
    },
10250
10251
    stateValue: function(composer, command) {
10252
      var st = this.state(composer, command),
10253
          styleStr, fontsizeMatches,
0 ignored issues
show
Unused Code introduced by
The variable fontsizeMatches seems to be never used. Consider removing it.
Loading history...
10254
          val = false;
0 ignored issues
show
Unused Code introduced by
The variable val seems to be never used. Consider removing it.
Loading history...
10255
10256
      if (st && wysihtml5.lang.object(st).isArray()) {
10257
          st = st[0];
10258
      }
10259
      if (st) {
10260
        styleStr = st.getAttribute('style');
10261
        if (styleStr) {
10262
          return wysihtml5.quirks.styleParser.parseFontSize(styleStr);
10263
        }
10264
      }
10265
      return false;
10266
    }
10267
  };
10268
})(wysihtml5);
10269
;/**
10270
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10271
 * which we don't want
10272
 * Instead we set a css class
10273
 */
10274
(function(wysihtml5) {
10275
  var REG_EXP = /wysiwyg-color-[0-9a-z]+/g;
10276
10277
  wysihtml5.commands.foreColor = {
10278
    exec: function(composer, command, color) {
10279
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10280
    },
10281
10282
    state: function(composer, command, color) {
10283
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10284
    }
10285
  };
10286
})(wysihtml5);
10287
;/**
10288
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10289
 * which we don't want
10290
 * Instead we set a css class
10291
 */
10292
(function(wysihtml5) {
10293
  var REG_EXP = /(\s|^)color\s*:\s*[^;\s]+;?/gi;
10294
10295
  wysihtml5.commands.foreColorStyle = {
10296
    exec: function(composer, command, color) {
10297
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "color:" + color.color : "color:" + color, "color"),
10298
          colString;
10299
10300
      if (colorVals) {
10301
        colString = "color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10302
        if (colorVals[3] !== 1) {
10303
          colString += "color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10304
        }
10305
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10306
      }
10307
    },
10308
10309
    state: function(composer, command) {
10310
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "color", REG_EXP);
10311
    },
10312
10313
    stateValue: function(composer, command, props) {
10314
      var st = this.state(composer, command),
10315
          colorStr;
10316
10317
      if (st && wysihtml5.lang.object(st).isArray()) {
10318
        st = st[0];
10319
      }
10320
10321
      if (st) {
10322
        colorStr = st.getAttribute('style');
10323
        if (colorStr) {
10324
          if (colorStr) {
10325
            val = wysihtml5.quirks.styleParser.parseColor(colorStr, "color");
0 ignored issues
show
Bug introduced by
The variable val seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.val.
Loading history...
10326
            return wysihtml5.quirks.styleParser.unparseColor(val, props);
10327
          }
10328
        }
10329
      }
10330
      return false;
10331
    }
10332
10333
  };
10334
})(wysihtml5);
10335
;/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
10336
(function(wysihtml5) {
10337
  var REG_EXP = /(\s|^)background-color\s*:\s*[^;\s]+;?/gi;
10338
10339
  wysihtml5.commands.bgColorStyle = {
10340
    exec: function(composer, command, color) {
10341
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "background-color:" + color.color : "background-color:" + color, "background-color"),
10342
          colString;
10343
10344
      if (colorVals) {
10345
        colString = "background-color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10346
        if (colorVals[3] !== 1) {
10347
          colString += "background-color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10348
        }
10349
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10350
      }
10351
    },
10352
10353
    state: function(composer, command) {
10354
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "background-color", REG_EXP);
10355
    },
10356
10357
    stateValue: function(composer, command, props) {
10358
      var st = this.state(composer, command),
10359
          colorStr,
10360
          val = false;
0 ignored issues
show
Unused Code introduced by
The assignment to variable val seems to be never used. Consider removing it.
Loading history...
10361
10362
      if (st && wysihtml5.lang.object(st).isArray()) {
10363
        st = st[0];
10364
      }
10365
10366
      if (st) {
10367
        colorStr = st.getAttribute('style');
10368
        if (colorStr) {
10369
          val = wysihtml5.quirks.styleParser.parseColor(colorStr, "background-color");
10370
          return wysihtml5.quirks.styleParser.unparseColor(val, props);
10371
        }
10372
      }
10373
      return false;
10374
    }
10375
10376
  };
10377
})(wysihtml5);
10378
;(function(wysihtml5) {
10379
  var dom                     = wysihtml5.dom,
10380
      // Following elements are grouped
10381
      // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
10382
      // instead of creating a H4 within a H1 which would result in semantically invalid html
10383
      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "DIV"];
10384
10385
  /**
10386
   * Remove similiar classes (based on classRegExp)
10387
   * and add the desired class name
10388
   */
10389
  function _addClass(element, className, classRegExp) {
10390
    if (element.className) {
10391
      _removeClass(element, classRegExp);
10392
      element.className = wysihtml5.lang.string(element.className + " " + className).trim();
10393
    } else {
10394
      element.className = className;
10395
    }
10396
  }
10397
10398
  function _addStyle(element, cssStyle, styleRegExp) {
10399
    _removeStyle(element, styleRegExp);
10400
    if (element.getAttribute('style')) {
10401
      element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
10402
    } else {
10403
      element.setAttribute('style', cssStyle);
10404
    }
10405
  }
10406
10407
  function _removeClass(element, classRegExp) {
10408
    var ret = classRegExp.test(element.className);
10409
    element.className = element.className.replace(classRegExp, "");
10410
    if (wysihtml5.lang.string(element.className).trim() == '') {
10411
        element.removeAttribute('class');
10412
    }
10413
    return ret;
10414
  }
10415
10416
  function _removeStyle(element, styleRegExp) {
10417
    var ret = styleRegExp.test(element.getAttribute('style'));
10418
    element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
10419
    if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
10420
      element.removeAttribute('style');
10421
    }
10422
    return ret;
10423
  }
10424
10425
  function _removeLastChildIfLineBreak(node) {
10426
    var lastChild = node.lastChild;
10427
    if (lastChild && _isLineBreak(lastChild)) {
10428
      lastChild.parentNode.removeChild(lastChild);
10429
    }
10430
  }
10431
10432
  function _isLineBreak(node) {
10433
    return node.nodeName === "BR";
10434
  }
10435
10436
  /**
10437
   * Execute native query command
10438
   * and if necessary modify the inserted node's className
10439
   */
10440
  function _execCommand(doc, composer, command, nodeName, className) {
0 ignored issues
show
introduced by
The function _execCommand does not seem to be used and can be removed.
Loading history...
10441
    var ranges = composer.selection.getOwnRanges();
10442
    for (var i = ranges.length; i--;){
10443
      composer.selection.getSelection().removeAllRanges();
10444
      composer.selection.setSelection(ranges[i]);
10445
      if (className) {
10446
        var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
10447
          var target = event.target,
10448
              displayStyle;
10449
          if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
10450
            return;
10451
          }
10452
          displayStyle = dom.getStyle("display").from(target);
10453
          if (displayStyle.substr(0, 6) !== "inline") {
10454
            // Make sure that only block elements receive the given class
10455
            target.className += " " + className;
10456
          }
10457
        });
10458
      }
10459
      doc.execCommand(command, false, nodeName);
10460
10461
      if (eventListener) {
10462
        eventListener.stop();
10463
      }
10464
    }
10465
  }
10466
10467
  function _selectionWrap(composer, options) {
10468
    if (composer.selection.isCollapsed()) {
10469
        composer.selection.selectLine();
10470
    }
10471
10472
    var surroundedNodes = composer.selection.surround(options);
10473
    for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
10474
      wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
10475
      _removeLastChildIfLineBreak(surroundedNodes[i]);
10476
    }
10477
10478
    // rethink restoring selection
10479
    // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
10480
  }
10481
10482
  function _hasClasses(element) {
10483
    return !!wysihtml5.lang.string(element.className).trim();
10484
  }
10485
10486
  function _hasStyles(element) {
10487
    return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
10488
  }
10489
10490
  wysihtml5.commands.formatBlock = {
10491
    exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10492
      var doc             = composer.doc,
0 ignored issues
show
Unused Code introduced by
The assignment to variable doc seems to be never used. Consider removing it.
Loading history...
10493
          blockElements    = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
10494
          useLineBreaks   = composer.config.useLineBreaks,
10495
          defaultNodeName = useLineBreaks ? "DIV" : "P",
10496
          selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
10497
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10498
10499
      if (blockElements.length) {
10500
        composer.selection.executeAndRestoreRangy(function() {
10501
          for (var b = blockElements.length; b--;) {
10502
            if (classRegExp) {
10503
              classRemoveAction = _removeClass(blockElements[b], classRegExp);
10504
            }
10505
            if (styleRegExp) {
10506
              styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
10507
            }
10508
10509
            if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
10510
              // dont rename or remove element when just setting block formating class or style
10511
              return;
10512
            }
10513
10514
            var hasClasses = _hasClasses(blockElements[b]),
10515
                hasStyles = _hasStyles(blockElements[b]);
10516
10517
            if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
10518
              // Insert a line break afterwards and beforewards when there are siblings
10519
              // that are not of type line break or block element
10520
              wysihtml5.dom.lineBreaks(blockElements[b]).add();
10521
              dom.replaceWithChildNodes(blockElements[b]);
10522
            } else {
10523
              // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
10524
              dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
10525
            }
10526
          }
10527
        });
10528
10529
        return;
10530
      }
10531
10532
      // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
10533
      if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
10534
        selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
10535
        composer.selection.executeAndRestoreRangy(function() {
10536
          for (var n = selectedNodes.length; n--;) {
10537
            blockElement = dom.getParentElement(selectedNodes[n], {
10538
              nodeName: BLOCK_ELEMENTS_GROUP
10539
            });
10540
            if (blockElement == composer.element) {
0 ignored issues
show
Bug introduced by
The variable blockElement is changed as part of the for loop for example by dom.getParentElement(sel...LEMENTS_GROUP,false))}) on line 10537. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
10541
              blockElement = null;
10542
            }
10543
            if (blockElement) {
10544
                // Rename current block element to new block element and add class
10545
                if (nodeName) {
10546
                  blockElement = dom.renameElement(blockElement, nodeName);
10547
                }
10548
                if (className) {
10549
                  _addClass(blockElement, className, classRegExp);
10550
                }
10551
                if (cssStyle) {
10552
                  _addStyle(blockElement, cssStyle, styleRegExp);
10553
                }
10554
              blockRenameFound = true;
10555
            }
10556
          }
10557
10558
        });
10559
10560
        if (blockRenameFound) {
10561
          return;
10562
        }
10563
      }
10564
10565
      _selectionWrap(composer, {
10566
        "nodeName": (nodeName || defaultNodeName),
10567
        "className": className || null,
10568
        "cssStyle": cssStyle || null
10569
      });
10570
    },
10571
10572
    state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10573
      var nodes = composer.selection.getSelectedOwnNodes(),
10574
          parents = [],
10575
          parent;
10576
10577
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10578
10579
      //var selectedNode = composer.selection.getSelectedNode();
10580
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
10581
        parent = dom.getParentElement(nodes[i], {
10582
          nodeName:     nodeName,
10583
          className:    className,
10584
          classRegExp:  classRegExp,
10585
          cssStyle:     cssStyle,
10586
          styleRegExp:  styleRegExp
10587
        });
10588
        if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
10589
          parents.push(parent);
10590
        }
10591
      }
10592
      if (parents.length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing parents.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
10593
        return false;
10594
      }
10595
      return parents;
10596
    }
10597
10598
10599
  };
10600
})(wysihtml5);
10601
;/* Formats block for as a <pre><code class="classname"></code></pre> block
10602
 * Useful in conjuction for sytax highlight utility: highlight.js
10603
 *
10604
 * Usage:
10605
 *
10606
 * editorInstance.composer.commands.exec("formatCode", "language-html");
10607
*/
10608
10609
wysihtml5.commands.formatCode = {
10610
10611
  exec: function(composer, command, classname) {
10612
    var pre = this.state(composer),
10613
        code, range, selectedNodes;
10614
    if (pre) {
10615
      // caret is already within a <pre><code>...</code></pre>
10616
      composer.selection.executeAndRestore(function() {
10617
        code = pre.querySelector("code");
10618
        wysihtml5.dom.replaceWithChildNodes(pre);
10619
        if (code) {
10620
          wysihtml5.dom.replaceWithChildNodes(code);
10621
        }
10622
      });
10623
    } else {
10624
      // Wrap in <pre><code>...</code></pre>
10625
      range = composer.selection.getRange();
10626
      selectedNodes = range.extractContents();
10627
      pre = composer.doc.createElement("pre");
10628
      code = composer.doc.createElement("code");
10629
10630
      if (classname) {
10631
        code.className = classname;
10632
      }
10633
10634
      pre.appendChild(code);
10635
      code.appendChild(selectedNodes);
10636
      range.insertNode(pre);
10637
      composer.selection.selectNode(pre);
10638
    }
10639
  },
10640
10641
  state: function(composer) {
10642
    var selectedNode = composer.selection.getSelectedNode();
10643
    if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
10644
        selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
10645
      return selectedNode;
10646
    } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
10647
      return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
10648
    }
10649
  }
10650
};;/**
10651
 * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
10652
 *
10653
 *   #1 caret in unformatted text:
10654
 *      abcdefg|
10655
 *   output:
10656
 *      abcdefg<b>|</b>
10657
 *
10658
 *   #2 unformatted text selected:
10659
 *      abc|deg|h
10660
 *   output:
10661
 *      abc<b>|deg|</b>h
10662
 *
10663
 *   #3 unformatted text selected across boundaries:
10664
 *      ab|c <span>defg|h</span>
10665
 *   output:
10666
 *      ab<b>|c </b><span><b>defg</b>|h</span>
10667
 *
10668
 *   #4 formatted text entirely selected
10669
 *      <b>|abc|</b>
10670
 *   output:
10671
 *      |abc|
10672
 *
10673
 *   #5 formatted text partially selected
10674
 *      <b>ab|c|</b>
10675
 *   output:
10676
 *      <b>ab</b>|c|
10677
 *
10678
 *   #6 formatted text selected across boundaries
10679
 *      <span>ab|c</span> <b>de|fgh</b>
10680
 *   output:
10681
 *      <span>ab|c</span> de|<b>fgh</b>
10682
 */
10683
(function(wysihtml5) {
10684
  var // Treat <b> as <strong> and vice versa
10685
      ALIAS_MAPPING = {
10686
        "strong": "b",
10687
        "em":     "i",
10688
        "b":      "strong",
10689
        "i":      "em"
10690
      },
10691
      htmlApplier = {};
10692
10693
  function _getTagNames(tagName) {
10694
    var alias = ALIAS_MAPPING[tagName];
10695
    return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
10696
  }
10697
10698
  function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
10699
    var identifier = tagName;
10700
    
10701
    if (className) {
10702
      identifier += ":" + className;
10703
    }
10704
    if (cssStyle) {
10705
      identifier += ":" + cssStyle;
10706
    }
10707
10708
    if (!htmlApplier[identifier]) {
10709
      htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
10710
    }
10711
10712
    return htmlApplier[identifier];
10713
  }
10714
10715
  wysihtml5.commands.formatInline = {
10716
    exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
10717
      var range = composer.selection.createRange(),
10718
          ownRanges = composer.selection.getOwnRanges();
10719
10720
      if (!ownRanges || ownRanges.length == 0) {
0 ignored issues
show
Best Practice introduced by
Comparing ownRanges.length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
10721
        return false;
10722
      }
10723
      composer.selection.getSelection().removeAllRanges();
10724
10725
      _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
10726
10727
      if (!dontRestoreSelect) {
10728
        range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
10729
        range.setEnd(
10730
          ownRanges[ownRanges.length - 1].endContainer,
10731
          ownRanges[ownRanges.length - 1].endOffset
10732
        );
10733
        composer.selection.setSelection(range);
10734
        composer.selection.executeAndRestore(function() {
10735
          if (!noCleanup) {
10736
            composer.cleanUp();
10737
          }
10738
        }, true, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
10739
      } else if (!noCleanup) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !noCleanup is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
10740
        composer.cleanUp();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
10741
      }
10742
    },
10743
10744
    // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
10745
    // It is achieved by selecting the entire state element before executing.
10746
    // This works on built in contenteditable inline format commands
10747
    execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
10748
      var that = this;
10749
10750
      if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
10751
        composer.selection.isCollapsed() &&
10752
        !composer.selection.caretIsLastInSelection() &&
10753
        !composer.selection.caretIsFirstInSelection()
10754
      ) {
10755
        var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
10756
        composer.selection.executeAndRestoreRangy(function() {
10757
          var parent = state_element.parentNode;
0 ignored issues
show
Unused Code introduced by
The variable parent seems to be never used. Consider removing it.
Loading history...
10758
          composer.selection.selectNode(state_element, true);
10759
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10760
        });
10761
      } else {
10762
        if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
10763
          composer.selection.executeAndRestoreRangy(function() {
10764
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10765
          });
10766
        } else {
10767
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
10768
        }
10769
      }
10770
    },
10771
10772
    state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
10773
      var doc           = composer.doc,
10774
          aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
10775
          ownRanges, isApplied;
10776
10777
      // Check whether the document contains a node with the desired tagName
10778
      if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
10779
          !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
10780
        return false;
10781
      }
10782
10783
       // Check whether the document contains a node with the desired className
10784
      if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
10785
         return false;
10786
      }
10787
10788
      ownRanges = composer.selection.getOwnRanges();
10789
10790
      if (!ownRanges || ownRanges.length === 0) {
10791
        return false;
10792
      }
10793
10794
      isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
10795
10796
      return (isApplied && isApplied.elements) ? isApplied.elements : false;
10797
    }
10798
  };
10799
})(wysihtml5);
10800
;(function(wysihtml5) {
10801
10802
  wysihtml5.commands.insertBlockQuote = {
10803
    exec: function(composer, command) {
10804
      var state = this.state(composer, command),
10805
          endToEndParent = composer.selection.isEndToEndInNode(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P']),
10806
          prevNode, nextNode;
0 ignored issues
show
Unused Code introduced by
The variable nextNode seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable prevNode seems to be never used. Consider removing it.
Loading history...
10807
10808
      composer.selection.executeAndRestore(function() {
10809
        if (state) {
10810
          if (composer.config.useLineBreaks) {
10811
             wysihtml5.dom.lineBreaks(state).add();
10812
          }
10813
          wysihtml5.dom.unwrap(state);
10814
        } else {
10815
          if (composer.selection.isCollapsed()) {
10816
            composer.selection.selectLine();
10817
          }
10818
          
10819
          if (endToEndParent) {
10820
            var qouteEl = endToEndParent.ownerDocument.createElement('blockquote');
10821
            wysihtml5.dom.insert(qouteEl).after(endToEndParent);
10822
            qouteEl.appendChild(endToEndParent);
10823
          } else {
10824
            composer.selection.surround({nodeName: "blockquote"});
10825
          }
10826
        }
10827
      });
10828
    },
10829
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
10830
      var selectedNode  = composer.selection.getSelectedNode(),
10831
          node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "BLOCKQUOTE" }, false, composer.element);
10832
10833
      return (node) ? node : false;
10834
    }
10835
  };
10836
10837
})(wysihtml5);;wysihtml5.commands.insertHTML = {
10838
  exec: function(composer, command, html) {
10839
    if (composer.commands.support(command)) {
10840
      composer.doc.execCommand(command, false, html);
10841
    } else {
10842
      composer.selection.insertHTML(html);
10843
    }
10844
  },
10845
10846
  state: function() {
10847
    return false;
10848
  }
10849
};
10850
;(function(wysihtml5) {
10851
  var NODE_NAME = "IMG";
10852
10853
  wysihtml5.commands.insertImage = {
10854
    /**
10855
     * Inserts an <img>
10856
     * If selection is already an image link, it removes it
10857
     *
10858
     * @example
10859
     *    // either ...
10860
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
10861
     *    // ... or ...
10862
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
10863
     */
10864
    exec: function(composer, command, value) {
10865
      value = typeof(value) === "object" ? value : { src: value };
10866
10867
      var doc     = composer.doc,
10868
          image   = this.state(composer),
10869
          textNode,
10870
          parent;
10871
10872
      if (image) {
10873
        // Image already selected, set the caret before it and delete it
10874
        composer.selection.setBefore(image);
10875
        parent = image.parentNode;
10876
        parent.removeChild(image);
10877
10878
        // and it's parent <a> too if it hasn't got any other relevant child nodes
10879
        wysihtml5.dom.removeEmptyTextNodes(parent);
10880
        if (parent.nodeName === "A" && !parent.firstChild) {
10881
          composer.selection.setAfter(parent);
10882
          parent.parentNode.removeChild(parent);
10883
        }
10884
10885
        // firefox and ie sometimes don't remove the image handles, even though the image got removed
10886
        wysihtml5.quirks.redraw(composer.element);
10887
        return;
10888
      }
10889
10890
      image = doc.createElement(NODE_NAME);
10891
10892
      for (var i in value) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
10893
        image.setAttribute(i === "className" ? "class" : i, value[i]);
10894
      }
10895
10896
      composer.selection.insertNode(image);
10897
      if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
10898
        textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
10899
        composer.selection.insertNode(textNode);
10900
        composer.selection.setAfter(textNode);
10901
      } else {
10902
        composer.selection.setAfter(image);
10903
      }
10904
    },
10905
10906
    state: function(composer) {
10907
      var doc = composer.doc,
10908
          selectedNode,
10909
          text,
10910
          imagesInSelection;
10911
10912
      if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) {
10913
        return false;
10914
      }
10915
10916
      selectedNode = composer.selection.getSelectedNode();
10917
      if (!selectedNode) {
10918
        return false;
10919
      }
10920
10921
      if (selectedNode.nodeName === NODE_NAME) {
10922
        // This works perfectly in IE
10923
        return selectedNode;
10924
      }
10925
10926
      if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) {
10927
        return false;
10928
      }
10929
10930
      text = composer.selection.getText();
10931
      text = wysihtml5.lang.string(text).trim();
10932
      if (text) {
10933
        return false;
10934
      }
10935
10936
      imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
10937
        return node.nodeName === "IMG";
10938
      });
10939
10940
      if (imagesInSelection.length !== 1) {
10941
        return false;
10942
      }
10943
10944
      return imagesInSelection[0];
10945
    }
10946
  };
10947
})(wysihtml5);
10948
;(function(wysihtml5) {
10949
  var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
10950
10951
  wysihtml5.commands.insertLineBreak = {
10952
    exec: function(composer, command) {
10953
      if (composer.commands.support(command)) {
10954
        composer.doc.execCommand(command, false, null);
10955
        if (!wysihtml5.browser.autoScrollsToCaret()) {
10956
          composer.selection.scrollIntoView();
10957
        }
10958
      } else {
10959
        composer.commands.exec("insertHTML", LINE_BREAK);
10960
      }
10961
    },
10962
10963
    state: function() {
10964
      return false;
10965
    }
10966
  };
10967
})(wysihtml5);
10968
;wysihtml5.commands.insertOrderedList = {
10969
  exec: function(composer, command) {
10970
    wysihtml5.commands.insertList.exec(composer, command, "OL");
10971
  },
10972
10973
  state: function(composer, command) {
10974
    return wysihtml5.commands.insertList.state(composer, command, "OL");
10975
  }
10976
};
10977
;wysihtml5.commands.insertUnorderedList = {
10978
  exec: function(composer, command) {
10979
    wysihtml5.commands.insertList.exec(composer, command, "UL");
10980
  },
10981
10982
  state: function(composer, command) {
10983
    return wysihtml5.commands.insertList.state(composer, command, "UL");
10984
  }
10985
};
10986
;wysihtml5.commands.insertList = (function(wysihtml5) {
10987
10988
  var isNode = function(node, name) {
10989
    if (node && node.nodeName) {
10990
      if (typeof name === 'string') {
10991
        name = [name];
10992
      }
10993
      for (var n = name.length; n--;) {
10994
        if (node.nodeName === name[n]) {
10995
          return true;
10996
        }
10997
      }
10998
    }
10999
    return false;
11000
  };
11001
11002
  var findListEl = function(node, nodeName, composer) {
11003
    var ret = {
11004
          el: null,
11005
          other: false
11006
        };
11007
11008
    if (node) {
11009
      var parentLi = wysihtml5.dom.getParentElement(node, { nodeName: "LI" }),
11010
          otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11011
11012
      if (isNode(node, nodeName)) {
11013
        ret.el = node;
11014
      } else if (isNode(node, otherNodeName)) {
11015
        ret = {
11016
          el: node,
11017
          other: true
11018
        };
11019
      } else if (parentLi) {
11020
        if (isNode(parentLi.parentNode, nodeName)) {
11021
          ret.el = parentLi.parentNode;
11022
        } else if (isNode(parentLi.parentNode, otherNodeName)) {
11023
          ret = {
11024
            el : parentLi.parentNode,
11025
            other: true
11026
          };
11027
        }
11028
      }
11029
    }
11030
11031
    // do not count list elements outside of composer
11032
    if (ret.el && !composer.element.contains(ret.el)) {
11033
      ret.el = null;
11034
    }
11035
11036
    return ret;
11037
  };
11038
11039
  var handleSameTypeList = function(el, nodeName, composer) {
11040
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
11041
        otherLists, innerLists;
0 ignored issues
show
Unused Code introduced by
The variable otherLists seems to be never used. Consider removing it.
Loading history...
11042
    // Unwrap list
11043
    // <ul><li>foo</li><li>bar</li></ul>
11044
    // becomes:
11045
    // foo<br>bar<br>
11046
    composer.selection.executeAndRestore(function() {
11047
      var otherLists = getListsInSelection(otherNodeName, composer);
11048
      if (otherLists.length) {
11049
        for (var l = otherLists.length; l--;) {
11050
          wysihtml5.dom.renameElement(otherLists[l], nodeName.toLowerCase());
11051
        }
11052
      } else {
11053
        innerLists = getListsInSelection(['OL', 'UL'], composer);
11054
        for (var i = innerLists.length; i--;) {
11055
          wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
11056
        }
11057
        wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
11058
      }
11059
    });
11060
  };
11061
11062
  var handleOtherTypeList =  function(el, nodeName, composer) {
11063
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11064
    // Turn an ordered list into an unordered list
11065
    // <ol><li>foo</li><li>bar</li></ol>
11066
    // becomes:
11067
    // <ul><li>foo</li><li>bar</li></ul>
11068
    // Also rename other lists in selection
11069
    composer.selection.executeAndRestore(function() {
11070
      var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
11071
11072
      // All selection inner lists get renamed too
11073
      for (var l = renameLists.length; l--;) {
11074
        wysihtml5.dom.renameElement(renameLists[l], nodeName.toLowerCase());
11075
      }
11076
    });
11077
  };
11078
11079
  var getListsInSelection = function(nodeName, composer) {
11080
      var ranges = composer.selection.getOwnRanges(),
11081
          renameLists = [];
11082
11083
      for (var r = ranges.length; r--;) {
11084
        renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
11085
          return isNode(node, nodeName);
11086
        }));
11087
      }
11088
11089
      return renameLists;
11090
  };
11091
11092
  var createListFallback = function(nodeName, composer) {
11093
    // Fallback for Create list
11094
    composer.selection.executeAndRestoreRangy(function() {
11095
      var tempClassName =  "_wysihtml5-temp-" + new Date().getTime(),
11096
          tempElement = composer.selection.deblockAndSurround({
11097
            "nodeName": "div",
11098
            "className": tempClassName
11099
          }),
11100
          isEmpty, list;
11101
11102
      // This space causes new lists to never break on enter 
11103
      var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
11104
      tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
11105
      
11106
      if (tempElement) {
11107
        isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
11108
        list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname);
11109
        if (isEmpty) {
11110
          composer.selection.selectNode(list.querySelector("li"), true);
11111
        }
11112
      }
11113
    });
11114
  };
11115
11116
  return {
11117
    exec: function(composer, command, nodeName) {
11118
      var doc           = composer.doc,
11119
          cmd           = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
11120
          selectedNode  = composer.selection.getSelectedNode(),
11121
          list          = findListEl(selectedNode, nodeName, composer);
11122
11123
      if (!list.el) {
11124
        if (composer.commands.support(cmd)) {
11125
          doc.execCommand(cmd, false, null);
11126
        } else {
11127
          createListFallback(nodeName, composer);
11128
        }
11129
      } else if (list.other) {
11130
        handleOtherTypeList(list.el, nodeName, composer);
11131
      } else {
11132
        handleSameTypeList(list.el, nodeName, composer);
11133
      }
11134
    },
11135
11136
    state: function(composer, command, nodeName) {
11137
      var selectedNode = composer.selection.getSelectedNode(),
11138
          list         = findListEl(selectedNode, nodeName, composer);
11139
11140
      return (list.el && !list.other) ? list.el : false;
11141
    }
11142
  };
11143
11144
})(wysihtml5);;wysihtml5.commands.italic = {
11145
  exec: function(composer, command) {
11146
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
11147
  },
11148
11149
  state: function(composer, command) {
11150
    // element.ownerDocument.queryCommandState("italic") results:
11151
    // firefox: only <i>
11152
    // chrome:  <i>, <em>, <blockquote>, ...
11153
    // ie:      <i>, <em>
11154
    // opera:   only <i>
11155
    return wysihtml5.commands.formatInline.state(composer, command, "i");
11156
  }
11157
};
11158
;(function(wysihtml5) {
11159
  var CLASS_NAME  = "wysiwyg-text-align-center",
11160
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11161
11162
  wysihtml5.commands.justifyCenter = {
11163
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11164
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11165
    },
11166
11167
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11168
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11169
    }
11170
  };
11171
})(wysihtml5);
11172
;(function(wysihtml5) {
11173
  var CLASS_NAME  = "wysiwyg-text-align-left",
11174
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11175
11176
  wysihtml5.commands.justifyLeft = {
11177
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11178
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11179
    },
11180
11181
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11182
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11183
    }
11184
  };
11185
})(wysihtml5);
11186
;(function(wysihtml5) {
11187
  var CLASS_NAME  = "wysiwyg-text-align-right",
11188
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11189
11190
  wysihtml5.commands.justifyRight = {
11191
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11192
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11193
    },
11194
11195
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11196
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11197
    }
11198
  };
11199
})(wysihtml5);
11200
;(function(wysihtml5) {
11201
  var CLASS_NAME  = "wysiwyg-text-align-justify",
11202
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11203
11204
  wysihtml5.commands.justifyFull = {
11205
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11206
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11207
    },
11208
11209
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11210
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11211
    }
11212
  };
11213
})(wysihtml5);
11214
;(function(wysihtml5) {
11215
  var STYLE_STR  = "text-align: right;",
11216
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11217
11218
  wysihtml5.commands.alignRightStyle = {
11219
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11220
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11221
    },
11222
11223
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11224
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11225
    }
11226
  };
11227
})(wysihtml5);
11228
;(function(wysihtml5) {
11229
  var STYLE_STR  = "text-align: left;",
11230
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11231
11232
  wysihtml5.commands.alignLeftStyle = {
11233
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11234
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11235
    },
11236
11237
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11238
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11239
    }
11240
  };
11241
})(wysihtml5);
11242
;(function(wysihtml5) {
11243
  var STYLE_STR  = "text-align: center;",
11244
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11245
11246
  wysihtml5.commands.alignCenterStyle = {
11247
    exec: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11248
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11249
    },
11250
11251
    state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11252
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11253
    }
11254
  };
11255
})(wysihtml5);
11256
;wysihtml5.commands.redo = {
11257
  exec: function(composer) {
11258
    return composer.undoManager.redo();
11259
  },
11260
11261
  state: function(composer) {
0 ignored issues
show
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11262
    return false;
11263
  }
11264
};
11265
;wysihtml5.commands.underline = {
11266
  exec: function(composer, command) {
11267
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "u");
11268
  },
11269
11270
  state: function(composer, command) {
11271
    return wysihtml5.commands.formatInline.state(composer, command, "u");
11272
  }
11273
};
11274
;wysihtml5.commands.undo = {
11275
  exec: function(composer) {
11276
    return composer.undoManager.undo();
11277
  },
11278
11279
  state: function(composer) {
0 ignored issues
show
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11280
    return false;
11281
  }
11282
};
11283
;wysihtml5.commands.createTable = {
11284
  exec: function(composer, command, value) {
11285
      var col, row, html;
11286
      if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
11287
          if (value.tableStyle) {
11288
            html = "<table style=\"" + value.tableStyle + "\">";
11289
          } else {
11290
            html = "<table>";
11291
          }
11292
          html += "<tbody>";
11293
          for (row = 0; row < value.rows; row ++) {
11294
              html += '<tr>';
11295
              for (col = 0; col < value.cols; col ++) {
11296
                  html += "<td>&nbsp;</td>";
11297
              }
11298
              html += '</tr>';
11299
          }
11300
          html += "</tbody></table>";
11301
          composer.commands.exec("insertHTML", html);
11302
          //composer.selection.insertHTML(html);
11303
      }
11304
11305
11306
  },
11307
11308
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11309
      return false;
11310
  }
11311
};
11312
;wysihtml5.commands.mergeTableCells = {
11313
  exec: function(composer, command) {
11314
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11315
          if (this.state(composer, command)) {
11316
              wysihtml5.dom.table.unmergeCell(composer.tableSelection.start);
11317
          } else {
11318
              wysihtml5.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end);
11319
          }
11320
      }
11321
  },
11322
11323
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11324
      if (composer.tableSelection) {
11325
          var start = composer.tableSelection.start,
11326
              end = composer.tableSelection.end;
11327
          if (start && end && start == end &&
11328
              ((
11329
                  wysihtml5.dom.getAttribute(start, "colspan") &&
11330
                  parseInt(wysihtml5.dom.getAttribute(start, "colspan"), 10) > 1
11331
              ) || (
11332
                  wysihtml5.dom.getAttribute(start, "rowspan") &&
11333
                  parseInt(wysihtml5.dom.getAttribute(start, "rowspan"), 10) > 1
11334
              ))
11335
          ) {
11336
              return [start];
11337
          }
11338
      }
11339
      return false;
11340
  }
11341
};
11342
;wysihtml5.commands.addTableCells = {
11343
  exec: function(composer, command, value) {
11344
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11345
11346
          // switches start and end if start is bigger than end (reverse selection)
11347
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
11348
          if (value == "before" || value == "above") {
11349
              wysihtml5.dom.table.addCells(tableSelect.start, value);
11350
          } else if (value == "after" || value == "below") {
11351
              wysihtml5.dom.table.addCells(tableSelect.end, value);
11352
          }
11353
          setTimeout(function() {
11354
              composer.tableSelection.select(tableSelect.start, tableSelect.end);
11355
          },0);
11356
      }
11357
  },
11358
11359
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11360
      return false;
11361
  }
11362
};
11363
;wysihtml5.commands.deleteTableCells = {
11364
  exec: function(composer, command, value) {
11365
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11366
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
11367
              idx = wysihtml5.dom.table.indexOf(tableSelect.start),
11368
              selCell,
11369
              table = composer.tableSelection.table;
11370
11371
          wysihtml5.dom.table.removeCells(tableSelect.start, value);
11372
          setTimeout(function() {
11373
              // move selection to next or previous if not present
11374
              selCell = wysihtml5.dom.table.findCell(table, idx);
11375
11376
              if (!selCell){
11377
                  if (value == "row") {
11378
                      selCell = wysihtml5.dom.table.findCell(table, {
11379
                          "row": idx.row - 1,
11380
                          "col": idx.col
11381
                      });
11382
                  }
11383
11384
                  if (value == "column") {
11385
                      selCell = wysihtml5.dom.table.findCell(table, {
11386
                          "row": idx.row,
11387
                          "col": idx.col - 1
11388
                      });
11389
                  }
11390
              }
11391
              if (selCell) {
11392
                  composer.tableSelection.select(selCell, selCell);
11393
              }
11394
          }, 0);
11395
11396
      }
11397
  },
11398
11399
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11400
      return false;
11401
  }
11402
};
11403
;wysihtml5.commands.indentList = {
11404
  exec: function(composer, command, value) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11405
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11406
    if (listEls) {
11407
      return this.tryToPushLiLevel(listEls, composer.selection);
11408
    }
11409
    return false;
11410
  },
11411
11412
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11413
      return false;
11414
  },
11415
11416
  tryToPushLiLevel: function(liNodes, selection) {
11417
    var listTag, list, prevLi, liNode, prevLiList,
11418
        found = false;
11419
11420
    selection.executeAndRestoreRangy(function() {
11421
11422
      for (var i = liNodes.length; i--;) {
11423
        liNode = liNodes[i];
11424
        listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL';
0 ignored issues
show
Bug introduced by
The variable liNode is changed as part of the for loop for example by liNodes.i on line 11423. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11425
        list = liNode.ownerDocument.createElement(listTag);
0 ignored issues
show
Bug introduced by
The variable listTag is changed as part of the for loop for example by liNode.parentNode.nodeName === "OL" ? "OL": "UL" on line 11424. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11426
        prevLi = wysihtml5.dom.domNode(liNode).prev({nodeTypes: [wysihtml5.ELEMENT_NODE]});
11427
        prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null;
0 ignored issues
show
Bug introduced by
The variable prevLi is changed as part of the for loop for example by wysihtml5.dom.domNode(li...EMENT_NODE,false)))))}) on line 11426. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11428
11429
        if (prevLi) {
11430
          if (prevLiList) {
0 ignored issues
show
Bug introduced by
The variable prevLiList is changed as part of the for loop for example by prevLi ? prevLi.querySelector("ul, ol"): null on line 11427. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11431
            prevLiList.appendChild(liNode);
11432
          } else {
11433
            list.appendChild(liNode);
0 ignored issues
show
Bug introduced by
The variable list is changed as part of the for loop for example by liNode.ownerDocument.createElement(listTag) on line 11425. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11434
            prevLi.appendChild(list);
11435
          }
11436
          found = true;
11437
        }
11438
      }
11439
11440
    });
11441
    return found;
11442
  }
11443
};
11444
;wysihtml5.commands.outdentList = {
11445
  exec: function(composer, command, value) {
0 ignored issues
show
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11446
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11447
    if (listEls) {
11448
      return this.tryToPullLiLevel(listEls, composer);
11449
    }
11450
    return false;
11451
  },
11452
11453
  state: function(composer, command) {
0 ignored issues
show
Unused Code introduced by
The parameter composer is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter command is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
11454
      return false;
11455
  },
11456
11457
  tryToPullLiLevel: function(liNodes, composer) {
11458
    var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList,
0 ignored issues
show
Unused Code introduced by
The variable prevLi seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable list seems to be never used. Consider removing it.
Loading history...
11459
        found = false,
11460
        that = this;
11461
11462
    composer.selection.executeAndRestoreRangy(function() {
11463
11464
      for (var i = liNodes.length; i--;) {
11465
        liNode = liNodes[i];
11466
        if (liNode.parentNode) {
0 ignored issues
show
Bug introduced by
The variable liNode is changed as part of the for loop for example by liNodes.i on line 11465. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11467
          listNode = liNode.parentNode;
11468
11469
          if (listNode.tagName === 'OL' || listNode.tagName === 'UL') {
0 ignored issues
show
Bug introduced by
The variable listNode is changed as part of the for loop for example by liNode.parentNode on line 11467. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11470
            found = true;
11471
11472
            outerListNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['OL', 'UL']}, false, composer.element);
11473
            outerLiNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['LI']}, false, composer.element);
11474
11475
            if (outerListNode && outerLiNode) {
0 ignored issues
show
Bug introduced by
The variable outerLiNode is changed as part of the for loop for example by wysihtml5.dom.getParentE...alse, composer.element) on line 11473. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
Bug introduced by
The variable outerListNode is changed as part of the for loop for example by wysihtml5.dom.getParentE...alse, composer.element) on line 11472. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11476
11477
              if (liNode.nextSibling) {
11478
                afterList = that.getAfterList(listNode, liNode);
11479
                liNode.appendChild(afterList);
0 ignored issues
show
Bug introduced by
The variable afterList is changed as part of the for loop for example by that.getAfterList(listNode, liNode) on line 11478. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11480
              }
11481
              outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
11482
11483
            } else {
11484
11485
              if (liNode.nextSibling) {
11486
                afterList = that.getAfterList(listNode, liNode);
11487
                liNode.appendChild(afterList);
11488
              }
11489
11490
              for (var j = liNode.childNodes.length; j--;) {
11491
                listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
11492
              }
11493
11494
              listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
11495
              liNode.parentNode.removeChild(liNode);
11496
11497
            }
11498
11499
            // cleanup
11500
            if (listNode.childNodes.length === 0) {
11501
                listNode.parentNode.removeChild(listNode);
11502
            }
11503
          }
11504
        }
11505
      }
11506
11507
    });
11508
    return found;
11509
  },
11510
11511
  getAfterList: function(listNode, liNode) {
11512
    var nodeName = listNode.nodeName,
11513
        newList = document.createElement(nodeName);
11514
11515
    while (liNode.nextSibling) {
11516
      newList.appendChild(liNode.nextSibling);
11517
    }
11518
    return newList;
11519
  }
11520
11521
};;/**
11522
 * Undo Manager for wysihtml5
11523
 * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
11524
 */
11525
(function(wysihtml5) {
11526
  var Z_KEY               = 90,
11527
      Y_KEY               = 89,
11528
      BACKSPACE_KEY       = 8,
11529
      DELETE_KEY          = 46,
11530
      MAX_HISTORY_ENTRIES = 25,
11531
      DATA_ATTR_NODE      = "data-wysihtml5-selection-node",
11532
      DATA_ATTR_OFFSET    = "data-wysihtml5-selection-offset",
11533
      UNDO_HTML           = '<span id="_wysihtml5-undo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
0 ignored issues
show
Unused Code introduced by
The variable UNDO_HTML seems to be never used. Consider removing it.
Loading history...
11534
      REDO_HTML           = '<span id="_wysihtml5-redo" class="_wysihtml5-temp">' + wysihtml5.INVISIBLE_SPACE + '</span>',
0 ignored issues
show
Unused Code introduced by
The variable REDO_HTML seems to be never used. Consider removing it.
Loading history...
11535
      dom                 = wysihtml5.dom;
11536
11537
  function cleanTempElements(doc) {
0 ignored issues
show
introduced by
The function cleanTempElements does not seem to be used and can be removed.
Loading history...
11538
    var tempElement;
11539
    while (tempElement = doc.querySelector("._wysihtml5-temp")) {
11540
      tempElement.parentNode.removeChild(tempElement);
11541
    }
11542
  }
11543
11544
  wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend(
11545
    /** @scope wysihtml5.UndoManager.prototype */ {
11546
    constructor: function(editor) {
11547
      this.editor = editor;
11548
      this.composer = editor.composer;
11549
      this.element = this.composer.element;
11550
11551
      this.position = 0;
11552
      this.historyStr = [];
11553
      this.historyDom = [];
11554
11555
      this.transact();
11556
11557
      this._observe();
11558
    },
11559
11560
    _observe: function() {
11561
      var that      = this,
11562
          doc       = this.composer.sandbox.getDocument(),
0 ignored issues
show
Unused Code introduced by
The variable doc seems to be never used. Consider removing it.
Loading history...
11563
          lastKey;
11564
11565
      // Catch CTRL+Z and CTRL+Y
11566
      dom.observe(this.element, "keydown", function(event) {
11567
        if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
11568
          return;
11569
        }
11570
11571
        var keyCode = event.keyCode,
11572
            isUndo = keyCode === Z_KEY && !event.shiftKey,
11573
            isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
11574
11575
        if (isUndo) {
11576
          that.undo();
11577
          event.preventDefault();
11578
        } else if (isRedo) {
11579
          that.redo();
11580
          event.preventDefault();
11581
        }
11582
      });
11583
11584
      // Catch delete and backspace
11585
      dom.observe(this.element, "keydown", function(event) {
11586
        var keyCode = event.keyCode;
11587
        if (keyCode === lastKey) {
11588
          return;
11589
        }
11590
11591
        lastKey = keyCode;
11592
11593
        if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
11594
          that.transact();
11595
        }
11596
      });
11597
11598
      this.editor
11599
        .on("newword:composer", function() {
11600
          that.transact();
11601
        })
11602
11603
        .on("beforecommand:composer", function() {
11604
          that.transact();
11605
        });
11606
    },
11607
11608
    transact: function() {
11609
      var previousHtml      = this.historyStr[this.position - 1],
11610
          currentHtml       = this.composer.getValue(false, false),
11611
          composerIsVisible   = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
11612
          range, node, offset, element, position;
11613
11614
      if (currentHtml === previousHtml) {
11615
        return;
11616
      }
11617
11618
      var length = this.historyStr.length = this.historyDom.length = this.position;
11619
      if (length > MAX_HISTORY_ENTRIES) {
11620
        this.historyStr.shift();
11621
        this.historyDom.shift();
11622
        this.position--;
11623
      }
11624
11625
      this.position++;
11626
11627
      if (composerIsVisible) {
11628
        // Do not start saving selection if composer is not visible
11629
        range   = this.composer.selection.getRange();
11630
        node    = (range && range.startContainer) ? range.startContainer : this.element;
11631
        offset  = (range && range.startOffset) ? range.startOffset : 0;
11632
11633
        if (node.nodeType === wysihtml5.ELEMENT_NODE) {
11634
          element = node;
11635
        } else {
11636
          element  = node.parentNode;
11637
          position = this.getChildNodeIndex(element, node);
11638
        }
11639
11640
        element.setAttribute(DATA_ATTR_OFFSET, offset);
11641
        if (typeof(position) !== "undefined") {
0 ignored issues
show
Bug introduced by
The variable position seems to not be initialized for all possible execution paths.
Loading history...
11642
          element.setAttribute(DATA_ATTR_NODE, position);
11643
        }
11644
      }
11645
11646
      var clone = this.element.cloneNode(!!currentHtml);
11647
      this.historyDom.push(clone);
11648
      this.historyStr.push(currentHtml);
11649
11650
      if (element) {
11651
        element.removeAttribute(DATA_ATTR_OFFSET);
11652
        element.removeAttribute(DATA_ATTR_NODE);
11653
      }
11654
11655
    },
11656
11657
    undo: function() {
11658
      this.transact();
11659
11660
      if (!this.undoPossible()) {
11661
        return;
11662
      }
11663
11664
      this.set(this.historyDom[--this.position - 1]);
11665
      this.editor.fire("undo:composer");
11666
    },
11667
11668
    redo: function() {
11669
      if (!this.redoPossible()) {
11670
        return;
11671
      }
11672
11673
      this.set(this.historyDom[++this.position - 1]);
11674
      this.editor.fire("redo:composer");
11675
    },
11676
11677
    undoPossible: function() {
11678
      return this.position > 1;
11679
    },
11680
11681
    redoPossible: function() {
11682
      return this.position < this.historyStr.length;
11683
    },
11684
11685
    set: function(historyEntry) {
11686
      this.element.innerHTML = "";
11687
11688
      var i = 0,
11689
          childNodes = historyEntry.childNodes,
11690
          length = historyEntry.childNodes.length;
11691
11692
      for (; i<length; i++) {
11693
        this.element.appendChild(childNodes[i].cloneNode(true));
11694
      }
11695
11696
      // Restore selection
11697
      var offset,
11698
          node,
11699
          position;
11700
11701
      if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
11702
        offset    = historyEntry.getAttribute(DATA_ATTR_OFFSET);
11703
        position  = historyEntry.getAttribute(DATA_ATTR_NODE);
11704
        node      = this.element;
11705
      } else {
11706
        node      = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
11707
        offset    = node.getAttribute(DATA_ATTR_OFFSET);
11708
        position  = node.getAttribute(DATA_ATTR_NODE);
11709
        node.removeAttribute(DATA_ATTR_OFFSET);
11710
        node.removeAttribute(DATA_ATTR_NODE);
11711
      }
11712
11713
      if (position !== null) {
11714
        node = this.getChildNodeByIndex(node, +position);
11715
      }
11716
11717
      this.composer.selection.set(node, offset);
11718
    },
11719
11720
    getChildNodeIndex: function(parent, child) {
11721
      var i           = 0,
11722
          childNodes  = parent.childNodes,
11723
          length      = childNodes.length;
11724
      for (; i<length; i++) {
11725
        if (childNodes[i] === child) {
11726
          return i;
11727
        }
11728
      }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
11729
    },
11730
11731
    getChildNodeByIndex: function(parent, index) {
11732
      return parent.childNodes[index];
11733
    }
11734
  });
11735
})(wysihtml5);
11736
;/**
11737
 * TODO: the following methods still need unit test coverage
11738
 */
11739
wysihtml5.views.View = Base.extend(
11740
  /** @scope wysihtml5.views.View.prototype */ {
11741
  constructor: function(parent, textareaElement, config) {
11742
    this.parent   = parent;
11743
    this.element  = textareaElement;
11744
    this.config   = config;
11745
    if (!this.config.noTextarea) {
11746
        this._observeViewChange();
11747
    }
11748
  },
11749
11750
  _observeViewChange: function() {
11751
    var that = this;
11752
    this.parent.on("beforeload", function() {
11753
      that.parent.on("change_view", function(view) {
11754
        if (view === that.name) {
11755
          that.parent.currentView = that;
11756
          that.show();
11757
          // Using tiny delay here to make sure that the placeholder is set before focusing
11758
          setTimeout(function() { that.focus(); }, 0);
11759
        } else {
11760
          that.hide();
11761
        }
11762
      });
11763
    });
11764
  },
11765
11766
  focus: function() {
11767
    if (this.element.ownerDocument.querySelector(":focus") === this.element) {
11768
      return;
11769
    }
11770
11771
    try { this.element.focus(); } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
11772
  },
11773
11774
  hide: function() {
11775
    this.element.style.display = "none";
11776
  },
11777
11778
  show: function() {
11779
    this.element.style.display = "";
11780
  },
11781
11782
  disable: function() {
11783
    this.element.setAttribute("disabled", "disabled");
11784
  },
11785
11786
  enable: function() {
11787
    this.element.removeAttribute("disabled");
11788
  }
11789
});
11790
;(function(wysihtml5) {
11791
  var dom       = wysihtml5.dom,
11792
      browser   = wysihtml5.browser;
11793
11794
  wysihtml5.views.Composer = wysihtml5.views.View.extend(
11795
    /** @scope wysihtml5.views.Composer.prototype */ {
11796
    name: "composer",
11797
11798
    // Needed for firefox in order to display a proper caret in an empty contentEditable
11799
    CARET_HACK: "<br>",
11800
11801
    constructor: function(parent, editableElement, config) {
11802
      this.base(parent, editableElement, config);
11803
      if (!this.config.noTextarea) {
11804
          this.textarea = this.parent.textarea;
11805
      } else {
11806
          this.editableArea = editableElement;
11807
      }
11808
      if (this.config.contentEditableMode) {
11809
          this._initContentEditableArea();
11810
      } else {
11811
          this._initSandbox();
11812
      }
11813
    },
11814
11815
    clear: function() {
11816
      this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
11817
    },
11818
11819
    getValue: function(parse, clearInternals) {
11820
      var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
11821
      if (parse !== false) {
11822
        value = this.parent.parse(value, (clearInternals === false) ? false : true);
11823
      }
11824
11825
      return value;
11826
    },
11827
11828
    setValue: function(html, parse) {
11829
      if (parse) {
11830
        html = this.parent.parse(html);
11831
      }
11832
11833
      try {
11834
        this.element.innerHTML = html;
11835
      } catch (e) {
11836
        this.element.innerText = html;
11837
      }
11838
    },
11839
11840
    cleanUp: function() {
11841
        this.parent.parse(this.element);
11842
    },
11843
11844
    show: function() {
11845
      this.editableArea.style.display = this._displayStyle || "";
11846
11847
      if (!this.config.noTextarea && !this.textarea.element.disabled) {
11848
        // Firefox needs this, otherwise contentEditable becomes uneditable
11849
        this.disable();
11850
        this.enable();
11851
      }
11852
    },
11853
11854
    hide: function() {
11855
      this._displayStyle = dom.getStyle("display").from(this.editableArea);
11856
      if (this._displayStyle === "none") {
11857
        this._displayStyle = null;
11858
      }
11859
      this.editableArea.style.display = "none";
11860
    },
11861
11862
    disable: function() {
11863
      this.parent.fire("disable:composer");
11864
      this.element.removeAttribute("contentEditable");
11865
    },
11866
11867
    enable: function() {
11868
      this.parent.fire("enable:composer");
11869
      this.element.setAttribute("contentEditable", "true");
11870
    },
11871
11872
    focus: function(setToEnd) {
11873
      // IE 8 fires the focus event after .focus()
11874
      // This is needed by our simulate_placeholder.js to work
11875
      // therefore we clear it ourselves this time
11876
      if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
11877
        this.clear();
11878
      }
11879
11880
      this.base();
11881
11882
      var lastChild = this.element.lastChild;
11883
      if (setToEnd && lastChild && this.selection) {
11884
        if (lastChild.nodeName === "BR") {
11885
          this.selection.setBefore(this.element.lastChild);
11886
        } else {
11887
          this.selection.setAfter(this.element.lastChild);
11888
        }
11889
      }
11890
    },
11891
11892
    getTextContent: function() {
11893
      return dom.getTextContent(this.element);
11894
    },
11895
11896
    hasPlaceholderSet: function() {
11897
      return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
11898
    },
11899
11900
    isEmpty: function() {
11901
      var innerHTML = this.element.innerHTML.toLowerCase();
11902
      return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML)  ||
11903
             innerHTML === ""            ||
11904
             innerHTML === "<br>"        ||
11905
             innerHTML === "<p></p>"     ||
11906
             innerHTML === "<p><br></p>" ||
11907
             this.hasPlaceholderSet();
11908
    },
11909
11910
    _initContentEditableArea: function() {
11911
        var that = this;
11912
11913
        if (this.config.noTextarea) {
11914
            this.sandbox = new dom.ContentEditableArea(function() {
11915
                that._create();
11916
            }, {}, this.editableArea);
11917
        } else {
11918
            this.sandbox = new dom.ContentEditableArea(function() {
11919
                that._create();
11920
            });
11921
            this.editableArea = this.sandbox.getContentEditable();
11922
            dom.insert(this.editableArea).after(this.textarea.element);
11923
            this._createWysiwygFormField();
11924
        }
11925
    },
11926
11927
    _initSandbox: function() {
11928
      var that = this;
11929
11930
      this.sandbox = new dom.Sandbox(function() {
11931
        that._create();
11932
      }, {
11933
        stylesheets:  this.config.stylesheets
11934
      });
11935
      this.editableArea  = this.sandbox.getIframe();
11936
11937
      var textareaElement = this.textarea.element;
11938
      dom.insert(this.editableArea).after(textareaElement);
11939
11940
      this._createWysiwygFormField();
11941
    },
11942
11943
    // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
11944
    _createWysiwygFormField: function() {
11945
        if (this.textarea.element.form) {
11946
          var hiddenField = document.createElement("input");
11947
          hiddenField.type   = "hidden";
11948
          hiddenField.name   = "_wysihtml5_mode";
11949
          hiddenField.value  = 1;
11950
          dom.insert(hiddenField).after(this.textarea.element);
11951
        }
11952
    },
11953
11954
    _create: function() {
11955
      var that = this;
11956
      this.doc                = this.sandbox.getDocument();
11957
      this.element            = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
11958
      if (!this.config.noTextarea) {
11959
          this.textarea           = this.parent.textarea;
11960
          this.element.innerHTML  = this.textarea.getValue(true, false);
11961
      } else {
11962
          this.cleanUp(); // cleans contenteditable on initiation as it may contain html
11963
      }
11964
11965
      // Make sure our selection handler is ready
11966
      this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname);
11967
11968
      // Make sure commands dispatcher is ready
11969
      this.commands  = new wysihtml5.Commands(this.parent);
11970
11971
      if (!this.config.noTextarea) {
11972
          dom.copyAttributes([
11973
              "className", "spellcheck", "title", "lang", "dir", "accessKey"
11974
          ]).from(this.textarea.element).to(this.element);
11975
      }
11976
11977
      dom.addClass(this.element, this.config.composerClassName);
11978
      //
11979
      // Make the editor look like the original textarea, by syncing styles
11980
      if (this.config.style && !this.config.contentEditableMode) {
11981
        this.style();
11982
      }
11983
11984
      this.observe();
11985
11986
      var name = this.config.name;
11987
      if (name) {
11988
        dom.addClass(this.element, name);
11989
        if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
11990
      }
11991
11992
      this.enable();
11993
11994
      if (!this.config.noTextarea && this.textarea.element.disabled) {
11995
        this.disable();
11996
      }
11997
11998
      // Simulate html5 placeholder attribute on contentEditable element
11999
      var placeholderText = typeof(this.config.placeholder) === "string"
12000
        ? this.config.placeholder
12001
        : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
12002
      if (placeholderText) {
12003
        dom.simulatePlaceholder(this.parent, this, placeholderText);
12004
      }
12005
12006
      // Make sure that the browser avoids using inline styles whenever possible
12007
      this.commands.exec("styleWithCSS", false);
12008
12009
      this._initAutoLinking();
12010
      this._initObjectResizing();
12011
      this._initUndoManager();
12012
      this._initLineBreaking();
12013
12014
      // Simulate html5 autofocus on contentEditable element
12015
      // This doesn't work on IOS (5.1.1)
12016
      if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
12017
        setTimeout(function() { that.focus(true); }, 100);
12018
      }
12019
12020
      // IE sometimes leaves a single paragraph, which can't be removed by the user
12021
      if (!browser.clearsContentEditableCorrectly()) {
12022
        wysihtml5.quirks.ensureProperClearing(this);
12023
      }
12024
12025
      // Set up a sync that makes sure that textarea and editor have the same content
12026
      if (this.initSync && this.config.sync) {
12027
        this.initSync();
12028
      }
12029
12030
      // Okay hide the textarea, we are ready to go
12031
      if (!this.config.noTextarea) { this.textarea.hide(); }
12032
12033
      // Fire global (before-)load event
12034
      this.parent.fire("beforeload").fire("load");
12035
    },
12036
12037
    _initAutoLinking: function() {
12038
      var that                           = this,
12039
          supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
12040
          supportsAutoLinking            = browser.doesAutoLinkingInContentEditable();
12041
      if (supportsDisablingOfAutoLinking) {
12042
        this.commands.exec("autoUrlDetect", false);
12043
      }
12044
12045
      if (!this.config.autoLink) {
12046
        return;
12047
      }
12048
12049
      // Only do the auto linking by ourselves when the browser doesn't support auto linking
12050
      // OR when he supports auto linking but we were able to turn it off (IE9+)
12051
      if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
12052
        this.parent.on("newword:composer", function() {
12053
          if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
12054
            that.selection.executeAndRestore(function(startContainer, endContainer) {
12055
              var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
12056
                  isInUneditable = false;
12057
12058
              for (var i = uneditables.length; i--;) {
12059
                if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
12060
                  isInUneditable = true;
12061
                }
12062
              }
12063
12064
              if (!isInUneditable) dom.autoLink(endContainer.parentNode, [that.config.uneditableContainerClassname]);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
12065
            });
12066
          }
12067
        });
12068
12069
        dom.observe(this.element, "blur", function() {
12070
          dom.autoLink(that.element, [that.config.uneditableContainerClassname]);
12071
        });
12072
      }
12073
12074
      // Assuming we have the following:
12075
      //  <a href="http://www.google.de">http://www.google.de</a>
12076
      // If a user now changes the url in the innerHTML we want to make sure that
12077
      // it's synchronized with the href attribute (as long as the innerHTML is still a url)
12078
      var // Use a live NodeList to check whether there are any links in the document
12079
          links           = this.sandbox.getDocument().getElementsByTagName("a"),
12080
          // The autoLink helper method reveals a reg exp to detect correct urls
12081
          urlRegExp       = dom.autoLink.URL_REG_EXP,
12082
          getTextContent  = function(element) {
12083
            var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim();
12084
            if (textContent.substr(0, 4) === "www.") {
12085
              textContent = "http://" + textContent;
12086
            }
12087
            return textContent;
12088
          };
12089
12090
      dom.observe(this.element, "keydown", function(event) {
12091
        if (!links.length) {
12092
          return;
12093
        }
12094
12095
        var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
12096
            link         = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
12097
            textContent;
12098
12099
        if (!link) {
12100
          return;
12101
        }
12102
12103
        textContent = getTextContent(link);
12104
        // keydown is fired before the actual content is changed
12105
        // therefore we set a timeout to change the href
12106
        setTimeout(function() {
12107
          var newTextContent = getTextContent(link);
12108
          if (newTextContent === textContent) {
12109
            return;
12110
          }
12111
12112
          // Only set href when new href looks like a valid url
12113
          if (newTextContent.match(urlRegExp)) {
12114
            link.setAttribute("href", newTextContent);
12115
          }
12116
        }, 0);
12117
      });
12118
    },
12119
12120
    _initObjectResizing: function() {
12121
      this.commands.exec("enableObjectResizing", true);
12122
12123
      // IE sets inline styles after resizing objects
12124
      // The following lines make sure that the width/height css properties
12125
      // are copied over to the width/height attributes
12126
      if (browser.supportsEvent("resizeend")) {
12127
        var properties        = ["width", "height"],
12128
            propertiesLength  = properties.length,
12129
            element           = this.element;
12130
12131
        dom.observe(element, "resizeend", function(event) {
12132
          var target = event.target || event.srcElement,
12133
              style  = target.style,
12134
              i      = 0,
12135
              property;
12136
12137
          if (target.nodeName !== "IMG") {
12138
            return;
12139
          }
12140
12141
          for (; i<propertiesLength; i++) {
12142
            property = properties[i];
12143
            if (style[property]) {
12144
              target.setAttribute(property, parseInt(style[property], 10));
12145
              style[property] = "";
12146
            }
12147
          }
12148
12149
          // After resizing IE sometimes forgets to remove the old resize handles
12150
          wysihtml5.quirks.redraw(element);
12151
        });
12152
      }
12153
    },
12154
12155
    _initUndoManager: function() {
12156
      this.undoManager = new wysihtml5.UndoManager(this.parent);
12157
    },
12158
12159
    _initLineBreaking: function() {
12160
      var that                              = this,
12161
          USE_NATIVE_LINE_BREAK_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
12162
          LIST_TAGS                         = ["UL", "OL", "MENU"];
12163
12164
      function adjust(selectedNode) {
12165
        var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
12166
        if (parentElement && dom.contains(that.element, parentElement)) {
12167
          that.selection.executeAndRestore(function() {
12168
            if (that.config.useLineBreaks) {
12169
              dom.replaceWithChildNodes(parentElement);
12170
            } else if (parentElement.nodeName !== "P") {
12171
              dom.renameElement(parentElement, "p");
12172
            }
12173
          });
12174
        }
12175
      }
12176
12177
      if (!this.config.useLineBreaks) {
12178
        dom.observe(this.element, ["focus", "keydown"], function() {
12179
          if (that.isEmpty()) {
12180
            var paragraph = that.doc.createElement("P");
12181
            that.element.innerHTML = "";
12182
            that.element.appendChild(paragraph);
12183
            if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
12184
              paragraph.innerHTML = "<br>";
12185
              that.selection.setBefore(paragraph.firstChild);
12186
            } else {
12187
              that.selection.selectNode(paragraph, true);
12188
            }
12189
          }
12190
        });
12191
      }
12192
12193
      // Under certain circumstances Chrome + Safari create nested <p> or <hX> tags after paste
12194
      // Inserting an invisible white space in front of it fixes the issue
12195
      // This is too hacky and causes selection not to replace content on paste in chrome
12196
     /* if (browser.createsNestedInvalidMarkupAfterPaste()) {
12197
        dom.observe(this.element, "paste", function(event) {
12198
          var invisibleSpace = that.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
12199
          that.selection.insertNode(invisibleSpace);
12200
        });
12201
      }*/
12202
12203
12204
      dom.observe(this.element, "keydown", function(event) {
12205
        var keyCode = event.keyCode;
12206
12207
        if (event.shiftKey) {
12208
          return;
12209
        }
12210
12211
        if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
12212
          return;
12213
        }
12214
        var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { nodeName: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
12215
        if (blockElement) {
12216
          setTimeout(function() {
12217
            // Unwrap paragraph after leaving a list or a H1-6
12218
            var selectedNode = that.selection.getSelectedNode(),
12219
                list;
12220
12221
            if (blockElement.nodeName === "LI") {
12222
              if (!selectedNode) {
12223
                return;
12224
              }
12225
12226
              list = dom.getParentElement(selectedNode, { nodeName: LIST_TAGS }, 2);
12227
12228
              if (!list) {
12229
                adjust(selectedNode);
12230
              }
12231
            }
12232
12233
            if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
12234
              adjust(selectedNode);
12235
            }
12236
          }, 0);
12237
          return;
12238
        }
12239
12240
        if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
12241
          event.preventDefault();
12242
          that.commands.exec("insertLineBreak");
12243
12244
        }
12245
      });
12246
    }
12247
  });
12248
})(wysihtml5);
12249
;(function(wysihtml5) {
12250
  var dom             = wysihtml5.dom,
12251
      doc             = document,
12252
      win             = window,
12253
      HOST_TEMPLATE   = doc.createElement("div"),
12254
      /**
12255
       * Styles to copy from textarea to the composer element
12256
       */
12257
      TEXT_FORMATTING = [
12258
        "background-color",
12259
        "color", "cursor",
12260
        "font-family", "font-size", "font-style", "font-variant", "font-weight",
12261
        "line-height", "letter-spacing",
12262
        "text-align", "text-decoration", "text-indent", "text-rendering",
12263
        "word-break", "word-wrap", "word-spacing"
12264
      ],
12265
      /**
12266
       * Styles to copy from textarea to the iframe
12267
       */
12268
      BOX_FORMATTING = [
12269
        "background-color",
12270
        "border-collapse",
12271
        "border-bottom-color", "border-bottom-style", "border-bottom-width",
12272
        "border-left-color", "border-left-style", "border-left-width",
12273
        "border-right-color", "border-right-style", "border-right-width",
12274
        "border-top-color", "border-top-style", "border-top-width",
12275
        "clear", "display", "float",
12276
        "margin-bottom", "margin-left", "margin-right", "margin-top",
12277
        "outline-color", "outline-offset", "outline-width", "outline-style",
12278
        "padding-left", "padding-right", "padding-top", "padding-bottom",
12279
        "position", "top", "left", "right", "bottom", "z-index",
12280
        "vertical-align", "text-align",
12281
        "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
12282
        "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
12283
        "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
12284
        "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
12285
        "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
12286
        "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
12287
        "width", "height"
12288
      ],
12289
      ADDITIONAL_CSS_RULES = [
12290
        "html                 { height: 100%; }",
12291
        "body                 { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
12292
        "body > p:first-child { margin-top: 0; }",
12293
        "._wysihtml5-temp     { display: none; }",
12294
        wysihtml5.browser.isGecko ?
12295
          "body.placeholder { color: graytext !important; }" :
12296
          "body.placeholder { color: #a9a9a9 !important; }",
12297
        // Ensure that user see's broken images and can delete them
12298
        "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
12299
      ];
12300
12301
  /**
12302
   * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
12303
   * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
12304
   *
12305
   * Other browsers need a more hacky way: (pssst don't tell my mama)
12306
   * In order to prevent the element being scrolled into view when focusing it, we simply
12307
   * move it out of the scrollable area, focus it, and reset it's position
12308
   */
12309
  var focusWithoutScrolling = function(element) {
12310
    if (element.setActive) {
12311
      // Following line could cause a js error when the textarea is invisible
12312
      // See https://github.com/xing/wysihtml5/issues/9
12313
      try { element.setActive(); } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
12314
    } else {
12315
      var elementStyle = element.style,
12316
          originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
12317
          originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
12318
          originalStyles = {
12319
            position:         elementStyle.position,
12320
            top:              elementStyle.top,
12321
            left:             elementStyle.left,
12322
            WebkitUserSelect: elementStyle.WebkitUserSelect
12323
          };
12324
12325
      dom.setStyles({
12326
        position:         "absolute",
12327
        top:              "-99999px",
12328
        left:             "-99999px",
12329
        // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
12330
        WebkitUserSelect: "none"
12331
      }).on(element);
12332
12333
      element.focus();
12334
12335
      dom.setStyles(originalStyles).on(element);
12336
12337
      if (win.scrollTo) {
12338
        // Some browser extensions unset this method to prevent annoyances
12339
        // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
12340
        // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
12341
        win.scrollTo(originalScrollLeft, originalScrollTop);
12342
      }
12343
    }
12344
  };
12345
12346
12347
  wysihtml5.views.Composer.prototype.style = function() {
12348
    var that                  = this,
12349
        originalActiveElement = doc.querySelector(":focus"),
12350
        textareaElement       = this.textarea.element,
12351
        hasPlaceholder        = textareaElement.hasAttribute("placeholder"),
12352
        originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder"),
12353
        originalDisplayValue  = textareaElement.style.display,
12354
        originalDisabled      = textareaElement.disabled,
12355
        displayValueForCopying;
12356
12357
    this.focusStylesHost      = HOST_TEMPLATE.cloneNode(false);
12358
    this.blurStylesHost       = HOST_TEMPLATE.cloneNode(false);
12359
    this.disabledStylesHost   = HOST_TEMPLATE.cloneNode(false);
12360
12361
    // Remove placeholder before copying (as the placeholder has an affect on the computed style)
12362
    if (hasPlaceholder) {
12363
      textareaElement.removeAttribute("placeholder");
12364
    }
12365
12366
    if (textareaElement === originalActiveElement) {
12367
      textareaElement.blur();
12368
    }
12369
12370
    // enable for copying styles
12371
    textareaElement.disabled = false;
12372
12373
    // set textarea to display="none" to get cascaded styles via getComputedStyle
12374
    textareaElement.style.display = displayValueForCopying = "none";
12375
12376
    if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
12377
        (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
12378
      textareaElement.style.display = displayValueForCopying = originalDisplayValue;
12379
    }
12380
12381
    // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
12382
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
12383
12384
    // --------- editor styles ---------
12385
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
12386
12387
    // --------- apply standard rules ---------
12388
    dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
12389
12390
    // --------- :disabled styles ---------
12391
    textareaElement.disabled = true;
12392
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12393
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12394
    textareaElement.disabled = originalDisabled;
12395
12396
    // --------- :focus styles ---------
12397
    textareaElement.style.display = originalDisplayValue;
12398
    focusWithoutScrolling(textareaElement);
12399
    textareaElement.style.display = displayValueForCopying;
12400
12401
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12402
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12403
12404
    // reset textarea
12405
    textareaElement.style.display = originalDisplayValue;
12406
12407
    dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
12408
12409
    // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
12410
    // this is needed for when the change_view event is fired where the iframe is hidden and then
12411
    // the blur event fires and re-displays it
12412
    var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]);
12413
12414
    // --------- restore focus ---------
12415
    if (originalActiveElement) {
12416
      originalActiveElement.focus();
12417
    } else {
12418
      textareaElement.blur();
12419
    }
12420
12421
    // --------- restore placeholder ---------
12422
    if (hasPlaceholder) {
12423
      textareaElement.setAttribute("placeholder", originalPlaceholder);
12424
    }
12425
12426
    // --------- Sync focus/blur styles ---------
12427
    this.parent.on("focus:composer", function() {
12428
      dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
12429
      dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
12430
    });
12431
12432
    this.parent.on("blur:composer", function() {
12433
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12434
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12435
    });
12436
12437
    this.parent.observe("disable:composer", function() {
12438
      dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
12439
      dom.copyStyles(TEXT_FORMATTING)     .from(that.disabledStylesHost).to(that.element);
12440
    });
12441
12442
    this.parent.observe("enable:composer", function() {
12443
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12444
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12445
    });
12446
12447
    return this;
12448
  };
12449
})(wysihtml5);
12450
;/**
12451
 * Taking care of events
12452
 *  - Simulating 'change' event on contentEditable element
12453
 *  - Handling drag & drop logic
12454
 *  - Catch paste events
12455
 *  - Dispatch proprietary newword:composer event
12456
 *  - Keyboard shortcuts
12457
 */
12458
(function(wysihtml5) {
12459
  var dom       = wysihtml5.dom,
12460
      browser   = wysihtml5.browser,
12461
      /**
12462
       * Map keyCodes to query commands
12463
       */
12464
      shortcuts = {
12465
        "66": "bold",     // B
12466
        "73": "italic",   // I
12467
        "85": "underline" // U
12468
      };
12469
12470
  var deleteAroundEditable = function(selection, uneditable, element) {
12471
    // merge node with previous node from uneditable
12472
    var prevNode = selection.getPreviousNode(uneditable, true),
12473
        curNode = selection.getSelectedNode();
12474
12475
    if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
12476
    if (prevNode) {
12477
      if (curNode.nodeType == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing curNode.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
12478
        var first = curNode.firstChild;
12479
12480
        if (prevNode.nodeType == 1) {
0 ignored issues
show
Best Practice introduced by
Comparing prevNode.nodeType to 1 using the == operator is not safe. Consider using === instead.
Loading history...
12481
          while (curNode.firstChild) {
12482
            prevNode.appendChild(curNode.firstChild);
12483
          }
12484
        } else {
12485
          while (curNode.firstChild) {
12486
            uneditable.parentNode.insertBefore(curNode.firstChild, uneditable);
12487
          }
12488
        }
12489
        if (curNode.parentNode) {
12490
          curNode.parentNode.removeChild(curNode);
12491
        }
12492
        selection.setBefore(first);
12493
      } else {
12494
        if (prevNode.nodeType == 1) {
12495
          prevNode.appendChild(curNode);
12496
        } else {
12497
          uneditable.parentNode.insertBefore(curNode, uneditable);
12498
        }
12499
        selection.setBefore(curNode);
12500
      }
12501
    }
12502
  };
12503
12504
  var handleDeleteKeyPress = function(event, selection, element, composer) {
12505
    if (selection.isCollapsed()) {
12506
      if (selection.caretIsInTheBeginnig('LI')) {
12507
        event.preventDefault();
12508
        composer.commands.exec('outdentList');
12509
      } else if (selection.caretIsInTheBeginnig()) {
12510
        event.preventDefault();
12511
      } else {
12512
12513
        if (selection.caretIsFirstInSelection() &&
12514
            selection.getPreviousNode() &&
12515
            selection.getPreviousNode().nodeName &&
12516
            (/^H\d$/gi).test(selection.getPreviousNode().nodeName)
12517
        ) {
12518
          var prevNode = selection.getPreviousNode();
12519
          event.preventDefault();
12520
          if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
12521
            // heading is empty
12522
            prevNode.parentNode.removeChild(prevNode);
12523
          } else {
12524
            var range = prevNode.ownerDocument.createRange();
12525
            range.selectNodeContents(prevNode);
12526
            range.collapse(false);
12527
            selection.setSelection(range);
12528
          }
12529
        }
12530
12531
        var beforeUneditable = selection.caretIsBeforeUneditable();
12532
        // Do a special delete if caret would delete uneditable
12533
        if (beforeUneditable) {
12534
          event.preventDefault();
12535
          deleteAroundEditable(selection, beforeUneditable, element);
12536
        }
12537
      }
12538
    } else {
12539
      if (selection.containsUneditable()) {
12540
        event.preventDefault();
12541
        selection.deleteContents();
12542
      }
12543
    }
12544
  };
12545
12546
  var handleTabKeyDown = function(composer, element) {
0 ignored issues
show
Unused Code introduced by
The parameter element is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
12547
    if (!composer.selection.isCollapsed()) {
12548
      composer.selection.deleteContents();
12549
    } else if (composer.selection.caretIsInTheBeginnig('LI')) {
12550
      if (composer.commands.exec('indentList')) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
12551
    }
12552
12553
    // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
12554
    composer.commands.exec("insertHTML", "&emsp;");
12555
  };
12556
12557
  wysihtml5.views.Composer.prototype.observe = function() {
12558
    var that                = this,
12559
        state               = this.getValue(false, false),
12560
        container           = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
12561
        element             = this.element,
12562
        focusBlurElement    = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
12563
        pasteEvents         = ["drop", "paste"],
12564
        interactionEvents   = ["drop", "paste", "mouseup", "focus", "keyup"];
12565
12566
    // --------- destroy:composer event ---------
12567
    dom.observe(container, "DOMNodeRemoved", function() {
12568
      clearInterval(domNodeRemovedInterval);
12569
      that.parent.fire("destroy:composer");
12570
    });
12571
12572
    // DOMNodeRemoved event is not supported in IE 8
12573
    if (!browser.supportsMutationEvents()) {
12574
        var domNodeRemovedInterval = setInterval(function() {
12575
          if (!dom.contains(document.documentElement, container)) {
12576
            clearInterval(domNodeRemovedInterval);
12577
            that.parent.fire("destroy:composer");
12578
          }
12579
        }, 250);
12580
    }
12581
12582
    // --------- User interaction tracking --
12583
12584
    dom.observe(focusBlurElement, interactionEvents, function() {
12585
      setTimeout(function() {
12586
        that.parent.fire("interaction").fire("interaction:composer");
12587
      }, 0);
12588
    });
12589
12590
12591
    if (this.config.handleTables) {
12592
      if(!this.tableClickHandle && this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
12593
        if (this.sandbox.getIframe) {
12594
          this.tableClickHandle = dom.observe(container , ["focus", "mouseup", "mouseover"], function() {
12595
            that.doc.execCommand("enableObjectResizing", false, "false");
12596
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12597
            that.tableClickHandle.stop();
12598
          });
12599
        } else {
12600
          setTimeout(function() {
12601
            that.doc.execCommand("enableObjectResizing", false, "false");
12602
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12603
          }, 0);
12604
        }
12605
      }
12606
      this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
12607
    }
12608
12609
    // --------- Focus & blur logic ---------
12610
    dom.observe(focusBlurElement, "focus", function(event) {
12611
      that.parent.fire("focus", event).fire("focus:composer", event);
12612
12613
      // Delay storing of state until all focus handler are fired
12614
      // especially the one which resets the placeholder
12615
      setTimeout(function() { state = that.getValue(false, false); }, 0);
12616
    });
12617
12618
    dom.observe(focusBlurElement, "blur", function(event) {
12619
      if (state !== that.getValue(false, false)) {
12620
        //create change event if supported (all except IE8)
12621
        var changeevent = event;
12622
        if(typeof Object.create == 'function') {
12623
          changeevent = Object.create(event, { type: { value: 'change' } });
12624
        }
12625
        that.parent.fire("change", changeevent).fire("change:composer", changeevent);
12626
      }
12627
      that.parent.fire("blur", event).fire("blur:composer", event);
12628
    });
12629
12630
    // --------- Drag & Drop logic ---------
12631
    dom.observe(element, "dragenter", function() {
12632
      that.parent.fire("unset_placeholder");
12633
    });
12634
12635
    dom.observe(element, pasteEvents, function(event) {
12636
      setTimeout(function() {
12637
        that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12638
      }, 0);
12639
    });
12640
12641
    // --------- neword event ---------
12642
    dom.observe(element, "keyup", function(event) {
12643
      var keyCode = event.keyCode;
12644
      if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
12645
        that.parent.fire("newword:composer");
12646
      }
12647
    });
12648
12649
    this.parent.on("paste:composer", function() {
12650
      setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
12651
    });
12652
12653
    // --------- Make sure that images are selected when clicking on them ---------
12654
    if (!browser.canSelectImagesInContentEditable()) {
12655
      dom.observe(element, "mousedown", function(event) {
12656
        var target = event.target;
12657
        var allImages = element.querySelectorAll('img'),
12658
            notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
12659
            myImages = wysihtml5.lang.array(allImages).without(notMyImages);
12660
12661
        if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
12662
          that.selection.selectNode(target);
12663
        }
12664
      });
12665
    }
12666
12667
    if (!browser.canSelectImagesInContentEditable()) {
12668
        dom.observe(element, "drop", function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
12669
            // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
12670
            setTimeout(function() {
12671
                that.selection.getSelection().removeAllRanges();
12672
            }, 0);
12673
        });
12674
    }
12675
12676
    if (browser.hasHistoryIssue() && browser.supportsSelectionModify()) {
12677
      dom.observe(element, "keydown", function(event) {
12678
        if (!event.metaKey && !event.ctrlKey) {
12679
          return;
12680
        }
12681
12682
        var keyCode   = event.keyCode,
12683
            win       = element.ownerDocument.defaultView,
12684
            selection = win.getSelection();
12685
12686
        if (keyCode === 37 || keyCode === 39) {
12687
          if (keyCode === 37) {
12688
            selection.modify("extend", "left", "lineboundary");
12689
            if (!event.shiftKey) {
12690
              selection.collapseToStart();
12691
            }
12692
          }
12693
          if (keyCode === 39) {
12694
            selection.modify("extend", "right", "lineboundary");
12695
            if (!event.shiftKey) {
12696
              selection.collapseToEnd();
12697
            }
12698
          }
12699
          event.preventDefault();
12700
        }
12701
      });
12702
    }
12703
12704
    // --------- Shortcut logic ---------
12705
    dom.observe(element, "keydown", function(event) {
12706
      var keyCode  = event.keyCode,
12707
          command  = shortcuts[keyCode];
12708
      if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
12709
        that.commands.exec(command);
12710
        event.preventDefault();
12711
      }
12712
      if (keyCode === 8) {
12713
        // delete key
12714
        handleDeleteKeyPress(event, that.selection, element, that);
12715
      } else if (that.config.handleTabKey && keyCode === 9) {
12716
        event.preventDefault();
12717
        handleTabKeyDown(that, element);
12718
      }
12719
    });
12720
12721
    // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
12722
    dom.observe(element, "keydown", function(event) {
12723
      var target  = that.selection.getSelectedNode(true),
12724
          keyCode = event.keyCode,
12725
          parent;
12726
      if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
12727
        parent = target.parentNode;
12728
        // delete the <img>
12729
        parent.removeChild(target);
12730
        // and it's parent <a> too if it hasn't got any other child nodes
12731
        if (parent.nodeName === "A" && !parent.firstChild) {
12732
          parent.parentNode.removeChild(parent);
12733
        }
12734
12735
        setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
12736
        event.preventDefault();
12737
      }
12738
    });
12739
12740
    // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
12741
    if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
12742
      dom.observe(container, "focus", function() {
12743
        setTimeout(function() {
12744
          if (that.doc.querySelector(":focus") !== that.element) {
12745
            that.focus();
12746
          }
12747
        }, 0);
12748
      });
12749
12750
      dom.observe(this.element, "blur", function() {
12751
        setTimeout(function() {
12752
          that.selection.getSelection().removeAllRanges();
12753
        }, 0);
12754
      });
12755
    }
12756
12757
    // --------- Show url in tooltip when hovering links or images ---------
12758
    var titlePrefixes = {
12759
      IMG: "Image: ",
12760
      A:   "Link: "
12761
    };
12762
12763
    dom.observe(element, "mouseover", function(event) {
12764
      var target   = event.target,
12765
          nodeName = target.nodeName,
12766
          title;
12767
      if (nodeName !== "A" && nodeName !== "IMG") {
12768
        return;
12769
      }
12770
      var hasTitle = target.hasAttribute("title");
12771
      if(!hasTitle){
12772
        title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
12773
        target.setAttribute("title", title);
12774
      }
12775
    });
12776
  };
12777
})(wysihtml5);
12778
;/**
12779
 * Class that takes care that the value of the composer and the textarea is always in sync
12780
 */
12781
(function(wysihtml5) {
12782
  var INTERVAL = 400;
12783
12784
  wysihtml5.views.Synchronizer = Base.extend(
12785
    /** @scope wysihtml5.views.Synchronizer.prototype */ {
12786
12787
    constructor: function(editor, textarea, composer) {
12788
      this.editor   = editor;
12789
      this.textarea = textarea;
12790
      this.composer = composer;
12791
12792
      this._observe();
12793
    },
12794
12795
    /**
12796
     * Sync html from composer to textarea
12797
     * Takes care of placeholders
12798
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
12799
     */
12800
    fromComposerToTextarea: function(shouldParseHtml) {
12801
      this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
12802
    },
12803
12804
    /**
12805
     * Sync value of textarea to composer
12806
     * Takes care of placeholders
12807
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
12808
     */
12809
    fromTextareaToComposer: function(shouldParseHtml) {
12810
      var textareaValue = this.textarea.getValue(false, false);
12811
      if (textareaValue) {
12812
        this.composer.setValue(textareaValue, shouldParseHtml);
12813
      } else {
12814
        this.composer.clear();
12815
        this.editor.fire("set_placeholder");
12816
      }
12817
    },
12818
12819
    /**
12820
     * Invoke syncing based on view state
12821
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
12822
     */
12823
    sync: function(shouldParseHtml) {
12824
      if (this.editor.currentView.name === "textarea") {
12825
        this.fromTextareaToComposer(shouldParseHtml);
12826
      } else {
12827
        this.fromComposerToTextarea(shouldParseHtml);
12828
      }
12829
    },
12830
12831
    /**
12832
     * Initializes interval-based syncing
12833
     * also makes sure that on-submit the composer's content is synced with the textarea
12834
     * immediately when the form gets submitted
12835
     */
12836
    _observe: function() {
12837
      var interval,
12838
          that          = this,
12839
          form          = this.textarea.element.form,
12840
          startInterval = function() {
12841
            interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
12842
          },
12843
          stopInterval  = function() {
12844
            clearInterval(interval);
12845
            interval = null;
12846
          };
12847
12848
      startInterval();
12849
12850
      if (form) {
12851
        // If the textarea is in a form make sure that after onreset and onsubmit the composer
12852
        // has the correct state
12853
        wysihtml5.dom.observe(form, "submit", function() {
12854
          that.sync(true);
12855
        });
12856
        wysihtml5.dom.observe(form, "reset", function() {
12857
          setTimeout(function() { that.fromTextareaToComposer(); }, 0);
12858
        });
12859
      }
12860
12861
      this.editor.on("change_view", function(view) {
12862
        if (view === "composer" && !interval) {
12863
          that.fromTextareaToComposer(true);
12864
          startInterval();
12865
        } else if (view === "textarea") {
12866
          that.fromComposerToTextarea(true);
12867
          stopInterval();
12868
        }
12869
      });
12870
12871
      this.editor.on("destroy:composer", stopInterval);
12872
    }
12873
  });
12874
})(wysihtml5);
12875
;wysihtml5.views.Textarea = wysihtml5.views.View.extend(
12876
  /** @scope wysihtml5.views.Textarea.prototype */ {
12877
  name: "textarea",
12878
12879
  constructor: function(parent, textareaElement, config) {
12880
    this.base(parent, textareaElement, config);
12881
12882
    this._observe();
12883
  },
12884
12885
  clear: function() {
12886
    this.element.value = "";
12887
  },
12888
12889
  getValue: function(parse) {
12890
    var value = this.isEmpty() ? "" : this.element.value;
12891
    if (parse !== false) {
12892
      value = this.parent.parse(value);
12893
    }
12894
    return value;
12895
  },
12896
12897
  setValue: function(html, parse) {
12898
    if (parse) {
12899
      html = this.parent.parse(html);
12900
    }
12901
    this.element.value = html;
12902
  },
12903
12904
  cleanUp: function() {
12905
      var html = this.parent.parse(this.element.value);
12906
      this.element.value = html;
12907
  },
12908
12909
  hasPlaceholderSet: function() {
12910
    var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),
12911
        placeholderText     = this.element.getAttribute("placeholder") || null,
12912
        value               = this.element.value,
12913
        isEmpty             = !value;
12914
    return (supportsPlaceholder && isEmpty) || (value === placeholderText);
12915
  },
12916
12917
  isEmpty: function() {
12918
    return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
12919
  },
12920
12921
  _observe: function() {
12922
    var element = this.element,
12923
        parent  = this.parent,
12924
        eventMapping = {
12925
          focusin:  "focus",
12926
          focusout: "blur"
12927
        },
12928
        /**
12929
         * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
12930
         * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
12931
         */
12932
        events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
12933
12934
    parent.on("beforeload", function() {
12935
      wysihtml5.dom.observe(element, events, function(event) {
12936
        var eventName = eventMapping[event.type] || event.type;
12937
        parent.fire(eventName).fire(eventName + ":textarea");
12938
      });
12939
12940
      wysihtml5.dom.observe(element, ["paste", "drop"], function() {
12941
        setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
12942
      });
12943
    });
12944
  }
12945
});
12946
;/**
12947
 * WYSIHTML5 Editor
12948
 *
12949
 * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
12950
 * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
12951
 *
12952
 * @events
12953
 *    load
12954
 *    beforeload (for internal use only)
12955
 *    focus
12956
 *    focus:composer
12957
 *    focus:textarea
12958
 *    blur
12959
 *    blur:composer
12960
 *    blur:textarea
12961
 *    change
12962
 *    change:composer
12963
 *    change:textarea
12964
 *    paste
12965
 *    paste:composer
12966
 *    paste:textarea
12967
 *    newword:composer
12968
 *    destroy:composer
12969
 *    undo:composer
12970
 *    redo:composer
12971
 *    beforecommand:composer
12972
 *    aftercommand:composer
12973
 *    enable:composer
12974
 *    disable:composer
12975
 *    change_view
12976
 */
12977
(function(wysihtml5) {
12978
  var undef;
12979
12980
  var defaultConfig = {
12981
    // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
12982
    name:                 undef,
0 ignored issues
show
Bug introduced by
The variable undef seems to be never initialized.
Loading history...
12983
    // Whether the editor should look like the textarea (by adopting styles)
12984
    style:                true,
12985
    // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
12986
    toolbar:              undef,
12987
    // Whether toolbar is displayed after init by script automatically.
12988
    // Can be set to false if toolobar is set to display only on editable area focus
12989
    showToolbarAfterInit: true,
12990
    // Whether urls, entered by the user should automatically become clickable-links
12991
    autoLink:             true,
12992
    // Includes table editing events and cell selection tracking
12993
    handleTables:         true,
12994
    // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
12995
    handleTabKey:         true,
12996
    // Object which includes parser rules to apply when html gets inserted via copy & paste
12997
    // See parser_rules/*.js for examples
12998
    parserRules:          { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
12999
    // Parser method to use when the user inserts content via copy & paste
13000
    parser:               wysihtml5.dom.parse,
13001
    // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
13002
    composerClassName:    "wysihtml5-editor",
13003
    // Class name to add to the body when the wysihtml5 editor is supported
13004
    bodyClassName:        "wysihtml5-supported",
13005
    // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
13006
    useLineBreaks:        true,
13007
    // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
13008
    stylesheets:          [],
13009
    // Placeholder text to use, defaults to the placeholder attribute on the textarea element
13010
    placeholderText:      undef,
13011
    // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
13012
    supportTouchDevices:  true,
13013
    // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
13014
    cleanUp:              true,
13015
    // Whether to use div instead of secure iframe
13016
    contentEditableMode: false,
13017
    // Classname of container that editor should not touch and pass through
13018
    // Pass false to disable
13019
    uneditableContainerClassname: "wysihtml5-uneditable-container"
13020
  };
13021
13022
  wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
13023
    /** @scope wysihtml5.Editor.prototype */ {
13024
    constructor: function(editableElement, config) {
13025
      this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
13026
      this.config           = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
13027
      this._isCompatible    = wysihtml5.browser.supported();
13028
13029
      if (this.editableElement.nodeName.toLowerCase() != "textarea") {
13030
          this.config.contentEditableMode = true;
13031
          this.config.noTextarea = true;
13032
      }
13033
      if (!this.config.noTextarea) {
13034
          this.textarea         = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
13035
          this.currentView      = this.textarea;
13036
      }
13037
13038
      // Sort out unsupported/unwanted browsers here
13039
      if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
13040
        var that = this;
13041
        setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
13042
        return;
13043
      }
13044
13045
      // Add class name to body, to indicate that the editor is supported
13046
      wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
13047
13048
      this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
13049
      this.currentView = this.composer;
13050
13051
      if (typeof(this.config.parser) === "function") {
13052
        this._initParser();
13053
      }
13054
13055
      this.on("beforeload", this.handleBeforeLoad);
13056
    },
13057
13058
    handleBeforeLoad: function() {
13059
        if (!this.config.noTextarea) {
13060
            this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
13061
        }
13062
        if (this.config.toolbar) {
13063
          this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);
13064
        }
13065
    },
13066
13067
    isCompatible: function() {
13068
      return this._isCompatible;
13069
    },
13070
13071
    clear: function() {
13072
      this.currentView.clear();
13073
      return this;
13074
    },
13075
13076
    getValue: function(parse, clearInternals) {
13077
      return this.currentView.getValue(parse, clearInternals);
13078
    },
13079
13080
    setValue: function(html, parse) {
13081
      this.fire("unset_placeholder");
13082
13083
      if (!html) {
13084
        return this.clear();
13085
      }
13086
13087
      this.currentView.setValue(html, parse);
13088
      return this;
13089
    },
13090
13091
    cleanUp: function() {
13092
        this.currentView.cleanUp();
13093
    },
13094
13095
    focus: function(setToEnd) {
13096
      this.currentView.focus(setToEnd);
13097
      return this;
13098
    },
13099
13100
    /**
13101
     * Deactivate editor (make it readonly)
13102
     */
13103
    disable: function() {
13104
      this.currentView.disable();
13105
      return this;
13106
    },
13107
13108
    /**
13109
     * Activate editor
13110
     */
13111
    enable: function() {
13112
      this.currentView.enable();
13113
      return this;
13114
    },
13115
13116
    isEmpty: function() {
13117
      return this.currentView.isEmpty();
13118
    },
13119
13120
    hasPlaceholderSet: function() {
13121
      return this.currentView.hasPlaceholderSet();
13122
    },
13123
13124
    parse: function(htmlOrElement, clearInternals) {
13125
      var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
13126
      var returnValue = this.config.parser(htmlOrElement, {
13127
        "rules": this.config.parserRules,
13128
        "cleanUp": this.config.cleanUp,
13129
        "context": parseContext,
13130
        "uneditableClass": this.config.uneditableContainerClassname,
13131
        "clearInternals" : clearInternals
13132
      });
13133
      if (typeof(htmlOrElement) === "object") {
13134
        wysihtml5.quirks.redraw(htmlOrElement);
13135
      }
13136
      return returnValue;
13137
    },
13138
13139
    /**
13140
     * Prepare html parser logic
13141
     *  - Observes for paste and drop
13142
     */
13143
    _initParser: function() {
13144
      this.on("paste:composer", function() {
13145
        var keepScrollPosition  = true,
13146
            that                = this;
13147
        that.composer.selection.executeAndRestore(function() {
13148
          wysihtml5.quirks.cleanPastedHTML(that.composer.element);
13149
          that.parse(that.composer.element);
13150
        }, keepScrollPosition);
13151
      });
13152
    }
13153
  });
13154
})(wysihtml5);
13155
;/**
13156
 * Toolbar Dialog
13157
 *
13158
 * @param {Element} link The toolbar link which causes the dialog to show up
13159
 * @param {Element} container The dialog container
13160
 *
13161
 * @example
13162
 *    <!-- Toolbar link -->
13163
 *    <a data-wysihtml5-command="insertImage">insert an image</a>
13164
 *
13165
 *    <!-- Dialog -->
13166
 *    <div data-wysihtml5-dialog="insertImage" style="display: none;">
13167
 *      <label>
13168
 *        URL: <input data-wysihtml5-dialog-field="src" value="http://">
13169
 *      </label>
13170
 *      <label>
13171
 *        Alternative text: <input data-wysihtml5-dialog-field="alt" value="">
13172
 *      </label>
13173
 *    </div>
13174
 *
13175
 *    <script>
13176
 *      var dialog = new wysihtml5.toolbar.Dialog(
13177
 *        document.querySelector("[data-wysihtml5-command='insertImage']"),
13178
 *        document.querySelector("[data-wysihtml5-dialog='insertImage']")
13179
 *      );
13180
 *      dialog.observe("save", function(attributes) {
13181
 *        // do something
13182
 *      });
13183
 *    </script>
13184
 */
13185
(function(wysihtml5) {
13186
  var dom                     = wysihtml5.dom,
13187
      CLASS_NAME_OPENED       = "wysihtml5-command-dialog-opened",
13188
      SELECTOR_FORM_ELEMENTS  = "input, select, textarea",
13189
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
13190
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
13191
13192
13193
  wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend(
13194
    /** @scope wysihtml5.toolbar.Dialog.prototype */ {
13195
    constructor: function(link, container) {
13196
      this.link       = link;
13197
      this.container  = container;
13198
    },
13199
13200
    _observe: function() {
13201
      if (this._observed) {
13202
        return;
13203
      }
13204
13205
      var that = this,
13206
          callbackWrapper = function(event) {
13207
            var attributes = that._serialize();
13208
            if (attributes == that.elementToChange) {
13209
              that.fire("edit", attributes);
13210
            } else {
13211
              that.fire("save", attributes);
13212
            }
13213
            that.hide();
13214
            event.preventDefault();
13215
            event.stopPropagation();
13216
          };
13217
13218
      dom.observe(that.link, "click", function() {
13219
        if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
13220
          setTimeout(function() { that.hide(); }, 0);
13221
        }
13222
      });
13223
13224
      dom.observe(this.container, "keydown", function(event) {
13225
        var keyCode = event.keyCode;
13226
        if (keyCode === wysihtml5.ENTER_KEY) {
13227
          callbackWrapper(event);
13228
        }
13229
        if (keyCode === wysihtml5.ESCAPE_KEY) {
13230
          that.fire("cancel");
13231
          that.hide();
13232
        }
13233
      });
13234
13235
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
13236
13237
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
13238
        that.fire("cancel");
13239
        that.hide();
13240
        event.preventDefault();
13241
        event.stopPropagation();
13242
      });
13243
13244
      var formElements  = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
13245
          i             = 0,
13246
          length        = formElements.length,
13247
          _clearInterval = function() { clearInterval(that.interval); };
13248
      for (; i<length; i++) {
13249
        dom.observe(formElements[i], "change", _clearInterval);
13250
      }
13251
13252
      this._observed = true;
13253
    },
13254
13255
    /**
13256
     * Grabs all fields in the dialog and puts them in key=>value style in an object which
13257
     * then gets returned
13258
     */
13259
    _serialize: function() {
13260
      var data    = this.elementToChange || {},
13261
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
13262
          length  = fields.length,
13263
          i       = 0;
13264
13265
      for (; i<length; i++) {
13266
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
13267
      }
13268
      return data;
13269
    },
13270
13271
    /**
13272
     * Takes the attributes of the "elementToChange"
13273
     * and inserts them in their corresponding dialog input fields
13274
     *
13275
     * Assume the "elementToChange" looks like this:
13276
     *    <a href="http://www.google.com" target="_blank">foo</a>
13277
     *
13278
     * and we have the following dialog:
13279
     *    <input type="text" data-wysihtml5-dialog-field="href" value="">
13280
     *    <input type="text" data-wysihtml5-dialog-field="target" value="">
13281
     *
13282
     * after calling _interpolate() the dialog will look like this
13283
     *    <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com">
13284
     *    <input type="text" data-wysihtml5-dialog-field="target" value="_blank">
13285
     *
13286
     * Basically it adopted the attribute values into the corresponding input fields
13287
     *
13288
     */
13289
    _interpolate: function(avoidHiddenFields) {
13290
      var field,
13291
          fieldName,
13292
          newValue,
13293
          focusedElement = document.querySelector(":focus"),
13294
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
13295
          length         = fields.length,
13296
          i              = 0;
13297
      for (; i<length; i++) {
13298
        field = fields[i];
13299
13300
        // Never change elements where the user is currently typing in
13301
        if (field === focusedElement) {
13302
          continue;
13303
        }
13304
13305
        // Don't update hidden fields
13306
        // See https://github.com/xing/wysihtml5/pull/14
13307
        if (avoidHiddenFields && field.type === "hidden") {
13308
          continue;
13309
        }
13310
13311
        fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
13312
        newValue  = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue;
13313
        field.value = newValue;
13314
      }
13315
    },
13316
13317
    /**
13318
     * Show the dialog element
13319
     */
13320
    show: function(elementToChange) {
13321
      if (dom.hasClass(this.link, CLASS_NAME_OPENED)) {
13322
        return;
13323
      }
13324
13325
      var that        = this,
13326
          firstField  = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
13327
      this.elementToChange = elementToChange;
13328
      this._observe();
13329
      this._interpolate();
13330
      if (elementToChange) {
13331
        this.interval = setInterval(function() { that._interpolate(true); }, 500);
13332
      }
13333
      dom.addClass(this.link, CLASS_NAME_OPENED);
13334
      this.container.style.display = "";
13335
      this.fire("show");
13336
      if (firstField && !elementToChange) {
13337
        try {
13338
          firstField.focus();
13339
        } catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
13340
      }
13341
    },
13342
13343
    /**
13344
     * Hide the dialog element
13345
     */
13346
    hide: function() {
13347
      clearInterval(this.interval);
13348
      this.elementToChange = null;
13349
      dom.removeClass(this.link, CLASS_NAME_OPENED);
13350
      this.container.style.display = "none";
13351
      this.fire("hide");
13352
    }
13353
  });
13354
})(wysihtml5);
13355
;/**
13356
 * Converts speech-to-text and inserts this into the editor
13357
 * As of now (2011/03/25) this only is supported in Chrome >= 11
13358
 *
13359
 * Note that it sends the recorded audio to the google speech recognition api:
13360
 * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
13361
 *
13362
 * Current HTML5 draft can be found here
13363
 * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
13364
 *
13365
 * "Accessing Google Speech API Chrome 11"
13366
 * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
13367
 */
13368
(function(wysihtml5) {
13369
  var dom = wysihtml5.dom;
13370
13371
  var linkStyles = {
13372
    position: "relative"
13373
  };
13374
13375
  var wrapperStyles = {
13376
    left:     0,
13377
    margin:   0,
13378
    opacity:  0,
13379
    overflow: "hidden",
13380
    padding:  0,
13381
    position: "absolute",
13382
    top:      0,
13383
    zIndex:   1
13384
  };
13385
13386
  var inputStyles = {
13387
    cursor:     "inherit",
13388
    fontSize:   "50px",
13389
    height:     "50px",
13390
    marginTop:  "-25px",
13391
    outline:    0,
13392
    padding:    0,
13393
    position:   "absolute",
13394
    right:      "-4px",
13395
    top:        "50%"
13396
  };
13397
13398
  var inputAttributes = {
13399
    "x-webkit-speech": "",
13400
    "speech":          ""
13401
  };
13402
13403
  wysihtml5.toolbar.Speech = function(parent, link) {
13404
    var input = document.createElement("input");
13405
    if (!wysihtml5.browser.supportsSpeechApiOn(input)) {
13406
      link.style.display = "none";
13407
      return;
13408
    }
13409
    var lang = parent.editor.textarea.element.getAttribute("lang");
13410
    if (lang) {
13411
      inputAttributes.lang = lang;
13412
    }
13413
13414
    var wrapper = document.createElement("div");
13415
13416
    wysihtml5.lang.object(wrapperStyles).merge({
13417
      width:  link.offsetWidth  + "px",
13418
      height: link.offsetHeight + "px"
13419
    });
13420
13421
    dom.insert(input).into(wrapper);
13422
    dom.insert(wrapper).into(link);
13423
13424
    dom.setStyles(inputStyles).on(input);
13425
    dom.setAttributes(inputAttributes).on(input);
13426
13427
    dom.setStyles(wrapperStyles).on(wrapper);
13428
    dom.setStyles(linkStyles).on(link);
13429
13430
    var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
13431
    dom.observe(input, eventName, function() {
13432
      parent.execCommand("insertText", input.value);
13433
      input.value = "";
13434
    });
13435
13436
    dom.observe(input, "click", function(event) {
13437
      if (dom.hasClass(link, "wysihtml5-command-disabled")) {
13438
        event.preventDefault();
13439
      }
13440
13441
      event.stopPropagation();
13442
    });
13443
  };
13444
})(wysihtml5);
13445
;/**
13446
 * Toolbar
13447
 *
13448
 * @param {Object} parent Reference to instance of Editor instance
13449
 * @param {Element} container Reference to the toolbar container element
13450
 *
13451
 * @example
13452
 *    <div id="toolbar">
13453
 *      <a data-wysihtml5-command="createLink">insert link</a>
13454
 *      <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
13455
 *    </div>
13456
 *
13457
 *    <script>
13458
 *      var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
13459
 *    </script>
13460
 */
13461
(function(wysihtml5) {
13462
  var CLASS_NAME_COMMAND_DISABLED   = "wysihtml5-command-disabled",
13463
      CLASS_NAME_COMMANDS_DISABLED  = "wysihtml5-commands-disabled",
13464
      CLASS_NAME_COMMAND_ACTIVE     = "wysihtml5-command-active",
13465
      CLASS_NAME_ACTION_ACTIVE      = "wysihtml5-action-active",
13466
      dom                           = wysihtml5.dom;
13467
13468
  wysihtml5.toolbar.Toolbar = Base.extend(
13469
    /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
13470
    constructor: function(editor, container, showOnInit) {
13471
      this.editor     = editor;
13472
      this.container  = typeof(container) === "string" ? document.getElementById(container) : container;
13473
      this.composer   = editor.composer;
13474
13475
      this._getLinks("command");
13476
      this._getLinks("action");
13477
13478
      this._observe();
13479
      if (showOnInit) { this.show(); }
13480
13481
      var speechInputLinks  = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
13482
          length            = speechInputLinks.length,
13483
          i                 = 0;
13484
      for (; i<length; i++) {
13485
        new wysihtml5.toolbar.Speech(this, speechInputLinks[i]);
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new wysihtml5.toolbar.Sp...is, speechInputLinks.i) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
13486
      }
13487
    },
13488
13489
    _getLinks: function(type) {
13490
      var links   = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
13491
          length  = links.length,
13492
          i       = 0,
13493
          mapping = this[type + "Mapping"] = {},
13494
          link,
13495
          group,
13496
          name,
13497
          value,
13498
          dialog;
13499
      for (; i<length; i++) {
13500
        link    = links[i];
13501
        name    = link.getAttribute("data-wysihtml5-" + type);
13502
        value   = link.getAttribute("data-wysihtml5-" + type + "-value");
13503
        group   = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
13504
        dialog  = this._getDialog(link, name);
13505
13506
        mapping[name + ":" + value] = {
13507
          link:   link,
13508
          group:  group,
13509
          name:   name,
13510
          value:  value,
13511
          dialog: dialog,
13512
          state:  false
13513
        };
13514
      }
13515
    },
13516
13517
    _getDialog: function(link, command) {
13518
      var that          = this,
13519
          dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
13520
          dialog,
13521
          caretBookmark;
13522
13523
      if (dialogElement) {
13524
        if (wysihtml5.toolbar["Dialog_" + command]) {
13525
            dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
13526
        } else {
13527
            dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
13528
        }
13529
13530
        dialog.on("show", function() {
13531
          caretBookmark = that.composer.selection.getBookmark();
13532
13533
          that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13534
        });
13535
13536
        dialog.on("save", function(attributes) {
13537
          if (caretBookmark) {
13538
            that.composer.selection.setBookmark(caretBookmark);
13539
          }
13540
          that._execCommand(command, attributes);
13541
13542
          that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13543
        });
13544
13545
        dialog.on("cancel", function() {
13546
          that.editor.focus(false);
13547
          that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13548
        });
13549
      }
13550
      return dialog;
0 ignored issues
show
Bug introduced by
The variable dialog does not seem to be initialized in case dialogElement on line 13523 is false. Are you sure this can never be the case?
Loading history...
13551
    },
13552
13553
    /**
13554
     * @example
13555
     *    var toolbar = new wysihtml5.Toolbar();
13556
     *    // Insert a <blockquote> element or wrap current selection in <blockquote>
13557
     *    toolbar.execCommand("formatBlock", "blockquote");
13558
     */
13559
    execCommand: function(command, commandValue) {
13560
      if (this.commandsDisabled) {
13561
        return;
13562
      }
13563
13564
      var commandObj = this.commandMapping[command + ":" + commandValue];
13565
13566
      // Show dialog when available
13567
      if (commandObj && commandObj.dialog && !commandObj.state) {
13568
        commandObj.dialog.show();
13569
      } else {
13570
        this._execCommand(command, commandValue);
13571
      }
13572
    },
13573
13574
    _execCommand: function(command, commandValue) {
13575
      // Make sure that composer is focussed (false => don't move caret to the end)
13576
      this.editor.focus(false);
13577
13578
      this.composer.commands.exec(command, commandValue);
13579
      this._updateLinkStates();
13580
    },
13581
13582
    execAction: function(action) {
13583
      var editor = this.editor;
13584
      if (action === "change_view") {
13585
        if (editor.textarea) {
13586
            if (editor.currentView === editor.textarea) {
13587
              editor.fire("change_view", "composer");
13588
            } else {
13589
              editor.fire("change_view", "textarea");
13590
            }
13591
        }
13592
      }
13593
      if (action == "showSource") {
13594
          editor.fire("showSource");
13595
      }
13596
    },
13597
13598
    _observe: function() {
13599
      var that      = this,
13600
          editor    = this.editor,
13601
          container = this.container,
13602
          links     = this.commandLinks.concat(this.actionLinks),
13603
          length    = links.length,
13604
          i         = 0;
13605
13606
      for (; i<length; i++) {
13607
        // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
13608
        // (you know, a:link { ... } doesn't match anchors with missing href attribute)
13609
        if (links[i].nodeName === "A") {
13610
          dom.setAttributes({
13611
            href:         "javascript:;",
13612
            unselectable: "on"
13613
          }).on(links[i]);
13614
        } else {
13615
          dom.setAttributes({ unselectable: "on" }).on(links[i]);
13616
        }
13617
      }
13618
13619
      // Needed for opera and chrome
13620
      dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
13621
13622
      dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
13623
        var link          = this,
13624
            command       = link.getAttribute("data-wysihtml5-command"),
13625
            commandValue  = link.getAttribute("data-wysihtml5-command-value");
13626
        that.execCommand(command, commandValue);
13627
        event.preventDefault();
13628
      });
13629
13630
      dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
13631
        var action = this.getAttribute("data-wysihtml5-action");
13632
        that.execAction(action);
13633
        event.preventDefault();
13634
      });
13635
13636
      editor.on("interaction:composer", function() {
13637
          that._updateLinkStates();
13638
      });
13639
13640
      editor.on("focus:composer", function() {
13641
        that.bookmark = null;
13642
      });
13643
13644
      if (this.editor.config.handleTables) {
13645
          editor.on("tableselect:composer", function() {
13646
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
13647
          });
13648
          editor.on("tableunselect:composer", function() {
13649
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
13650
          });
13651
      }
13652
13653
      editor.on("change_view", function(currentView) {
13654
        // Set timeout needed in order to let the blur event fire first
13655
        if (editor.textarea) {
13656
            setTimeout(function() {
13657
              that.commandsDisabled = (currentView !== "composer");
13658
              that._updateLinkStates();
13659
              if (that.commandsDisabled) {
13660
                dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
13661
              } else {
13662
                dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
13663
              }
13664
            }, 0);
13665
        }
13666
      });
13667
    },
13668
13669
    _updateLinkStates: function() {
13670
13671
      var commandMapping    = this.commandMapping,
13672
          actionMapping     = this.actionMapping,
13673
          i,
13674
          state,
13675
          action,
13676
          command;
13677
      // every millisecond counts... this is executed quite often
13678
      for (i in commandMapping) {
13679
        command = commandMapping[i];
13680
        if (this.commandsDisabled) {
13681
          state = false;
13682
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13683
          if (command.group) {
13684
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13685
          }
13686
          if (command.dialog) {
13687
            command.dialog.hide();
13688
          }
13689
        } else {
13690
          state = this.composer.commands.state(command.name, command.value);
13691
          dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
13692
          if (command.group) {
13693
            dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
13694
          }
13695
        }
13696
        if (command.state === state) {
13697
          continue;
13698
        }
13699
13700
        command.state = state;
13701
        if (state) {
13702
          dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13703
          if (command.group) {
13704
            dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13705
          }
13706
          if (command.dialog) {
13707
            if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
13708
13709
              if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
13710
                // Grab first and only object/element in state array, otherwise convert state into boolean
13711
                // to avoid showing a dialog for multiple selected elements which may have different attributes
13712
                // eg. when two links with different href are selected, the state will be an array consisting of both link elements
13713
                // but the dialog interface can only update one
13714
                state = state.length === 1 ? state[0] : true;
13715
                command.state = state;
13716
              }
13717
              command.dialog.show(state);
13718
            } else {
13719
              command.dialog.hide();
13720
            }
13721
          }
13722
        } else {
13723
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13724
          if (command.group) {
13725
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13726
          }
13727
          if (command.dialog) {
13728
            command.dialog.hide();
13729
          }
13730
        }
13731
      }
13732
13733
      for (i in actionMapping) {
13734
        action = actionMapping[i];
13735
13736
        if (action.name === "change_view") {
13737
          action.state = this.editor.currentView === this.editor.textarea;
13738
          if (action.state) {
13739
            dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
13740
          } else {
13741
            dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
13742
          }
13743
        }
13744
      }
13745
    },
13746
13747
    show: function() {
13748
      this.container.style.display = "";
13749
    },
13750
13751
    hide: function() {
13752
      this.container.style.display = "none";
13753
    }
13754
  });
13755
13756
})(wysihtml5);
13757
;(function(wysihtml5) {
13758
    wysihtml5.toolbar.Dialog_createTable = wysihtml5.toolbar.Dialog.extend({
13759
        show: function(elementToChange) {
13760
            this.base(elementToChange);
13761
13762
        }
13763
13764
    });
13765
})(wysihtml5);
13766
;(function(wysihtml5) {
13767
  var dom                     = wysihtml5.dom,
0 ignored issues
show
Unused Code introduced by
The assignment to variable dom seems to be never used. Consider removing it.
Loading history...
13768
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
13769
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
13770
13771
  wysihtml5.toolbar.Dialog_foreColorStyle = wysihtml5.toolbar.Dialog.extend({
13772
    multiselect: true,
13773
13774
    _serialize: function() {
13775
      var data    = {},
13776
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
13777
          length  = fields.length,
13778
          i       = 0;
13779
13780
      for (; i<length; i++) {
13781
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
13782
      }
13783
      return data;
13784
    },
13785
13786
    _interpolate: function(avoidHiddenFields) {
13787
      var field,
13788
          fieldName,
0 ignored issues
show
Unused Code introduced by
The variable fieldName seems to be never used. Consider removing it.
Loading history...
13789
          newValue,
0 ignored issues
show
Unused Code introduced by
The variable newValue seems to be never used. Consider removing it.
Loading history...
13790
          focusedElement = document.querySelector(":focus"),
13791
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
13792
          length         = fields.length,
13793
          i              = 0,
13794
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
13795
          colorStr       = (firstElement) ? firstElement.getAttribute('style') : null,
13796
          color          = (colorStr) ? wysihtml5.quirks.styleParser.parseColor(colorStr, "color") : null;
13797
13798
      for (; i<length; i++) {
13799
        field = fields[i];
13800
        // Never change elements where the user is currently typing in
13801
        if (field === focusedElement) {
13802
          continue;
13803
        }
13804
        // Don't update hidden fields3
13805
        if (avoidHiddenFields && field.type === "hidden") {
13806
          continue;
13807
        }
13808
        if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
13809
          if (color) {
13810
            if (color[3] && color[3] != 1) {
0 ignored issues
show
Best Practice introduced by
Comparing color.3 to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
13811
              field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
13812
            } else {
13813
              field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
13814
            }
13815
          } else {
13816
            field.value = "rgb(0,0,0);";
13817
          }
13818
        }
13819
      }
13820
    }
13821
13822
  });
13823
})(wysihtml5);
13824
;(function(wysihtml5) {
13825
  var dom                     = wysihtml5.dom,
0 ignored issues
show
Unused Code introduced by
The assignment to variable dom seems to be never used. Consider removing it.
Loading history...
13826
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
0 ignored issues
show
Unused Code introduced by
The variable SELECTOR_FIELDS seems to be never used. Consider removing it.
Loading history...
13827
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
0 ignored issues
show
Unused Code introduced by
The variable ATTRIBUTE_FIELDS seems to be never used. Consider removing it.
Loading history...
13828
13829
  wysihtml5.toolbar.Dialog_fontSizeStyle = wysihtml5.toolbar.Dialog.extend({
13830
    multiselect: true,
13831
13832
    _serialize: function() {
13833
      return {"size" : this.container.querySelector('[data-wysihtml5-dialog-field="size"]').value};
13834
    },
13835
13836
    _interpolate: function(avoidHiddenFields) {
0 ignored issues
show
Unused Code introduced by
The parameter avoidHiddenFields is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13837
      var focusedElement = document.querySelector(":focus"),
13838
          field          = this.container.querySelector("[data-wysihtml5-dialog-field='size']"),
13839
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
13840
          styleStr       = (firstElement) ? firstElement.getAttribute('style') : null,
13841
          size           = (styleStr) ? wysihtml5.quirks.styleParser.parseFontSize(styleStr) : null;
13842
13843
      if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) {
13844
        field.value = size;
13845
      }
13846
    }
13847
13848
  });
13849
})(wysihtml5);
13850
/*!
13851
13852
 handlebars v1.3.0
13853
13854
Copyright (C) 2011 by Yehuda Katz
13855
13856
Permission is hereby granted, free of charge, to any person obtaining a copy
13857
of this software and associated documentation files (the "Software"), to deal
13858
in the Software without restriction, including without limitation the rights
13859
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13860
copies of the Software, and to permit persons to whom the Software is
13861
furnished to do so, subject to the following conditions:
13862
13863
The above copyright notice and this permission notice shall be included in
13864
all copies or substantial portions of the Software.
13865
13866
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13867
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13868
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13869
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
13870
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
13871
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
13872
THE SOFTWARE.
13873
13874
@license
13875
*/
13876
var Handlebars=function(){var a=function(){"use strict";function a(a){this.string=a}var b;return a.prototype.toString=function(){return""+this.string},b=a}(),b=function(a){"use strict";function b(a){return h[a]||"&amp;"}function c(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])}function d(a){return a instanceof g?a.toString():a||0===a?(a=""+a,j.test(a)?a.replace(i,b):a):""}function e(a){return a||0===a?m(a)&&0===a.length?!0:!1:!0}var f={},g=a,h={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},i=/[&<>"'`]/g,j=/[&<>"'`]/;f.extend=c;var k=Object.prototype.toString;f.toString=k;var l=function(a){return"function"==typeof a};l(/x/)&&(l=function(a){return"function"==typeof a&&"[object Function]"===k.call(a)});var l;f.isFunction=l;var m=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===k.call(a):!1};return f.isArray=m,f.escapeExpression=d,f.isEmpty=e,f}(a),c=function(){"use strict";function a(a,b){var d;b&&b.firstLine&&(d=b.firstLine,a+=" - "+d+":"+b.firstColumn);for(var e=Error.prototype.constructor.call(this,a),f=0;f<c.length;f++)this[c[f]]=e[c[f]];d&&(this.lineNumber=d,this.column=b.firstColumn)}var b,c=["description","fileName","lineNumber","message","name","number","stack"];return a.prototype=new Error,b=a}(),d=function(a,b){"use strict";function c(a,b){this.helpers=a||{},this.partials=b||{},d(this)}function d(a){a.registerHelper("helperMissing",function(a){if(2===arguments.length)return void 0;throw new h("Missing helper: '"+a+"'")}),a.registerHelper("blockHelperMissing",function(b,c){var d=c.inverse||function(){},e=c.fn;return m(b)&&(b=b.call(this)),b===!0?e(this):b===!1||null==b?d(this):l(b)?b.length>0?a.helpers.each(b,c):d(this):e(b)}),a.registerHelper("each",function(a,b){var c,d=b.fn,e=b.inverse,f=0,g="";if(m(a)&&(a=a.call(this)),b.data&&(c=q(b.data)),a&&"object"==typeof a)if(l(a))for(var h=a.length;h>f;f++)c&&(c.index=f,c.first=0===f,c.last=f===a.length-1),g+=d(a[f],{data:c});else for(var i in a)a.hasOwnProperty(i)&&(c&&(c.key=i,c.index=f,c.first=0===f),g+=d(a[i],{data:c}),f++);return 0===f&&(g=e(this)),g}),a.registerHelper("if",function(a,b){return m(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||g.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})}),a.registerHelper("with",function(a,b){return m(a)&&(a=a.call(this)),g.isEmpty(a)?void 0:b.fn(a)}),a.registerHelper("log",function(b,c){var d=c.data&&null!=c.data.level?parseInt(c.data.level,10):1;a.log(d,b)})}function e(a,b){p.log(a,b)}var f={},g=a,h=b,i="1.3.0";f.VERSION=i;var j=4;f.COMPILER_REVISION=j;var k={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:">= 1.0.0"};f.REVISION_CHANGES=k;var l=g.isArray,m=g.isFunction,n=g.toString,o="[object Object]";f.HandlebarsEnvironment=c,c.prototype={constructor:c,logger:p,log:e,registerHelper:function(a,b,c){if(n.call(a)===o){if(c||b)throw new h("Arg not supported with multiple helpers");g.extend(this.helpers,a)}else c&&(b.not=c),this.helpers[a]=b},registerPartial:function(a,b){n.call(a)===o?g.extend(this.partials,a):this.partials[a]=b}};var p={methodMap:{0:"debug",1:"info",2:"warn",3:"error"},DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,log:function(a,b){if(p.level<=a){var c=p.methodMap[a];"undefined"!=typeof console&&console[c]&&console[c].call(console,b)}}};f.logger=p,f.log=e;var q=function(a){var b={};return g.extend(b,a),b};return f.createFrame=q,f}(b,c),e=function(a,b,c){"use strict";function d(a){var b=a&&a[0]||1,c=m;if(b!==c){if(c>b){var d=n[c],e=n[b];throw new l("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+d+") or downgrade your runtime to an older version ("+e+").")}throw new l("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function e(a,b){if(!b)throw new l("No environment passed to template");var c=function(a,c,d,e,f,g){var h=b.VM.invokePartial.apply(this,arguments);if(null!=h)return h;if(b.compile){var i={helpers:e,partials:f,data:g};return f[c]=b.compile(a,{data:void 0!==g},b),f[c](d,i)}throw new l("The partial "+c+" could not be compiled when running in runtime-only mode")},d={escapeExpression:k.escapeExpression,invokePartial:c,programs:[],program:function(a,b,c){var d=this.programs[a];return c?d=g(a,b,c):d||(d=this.programs[a]=g(a,b)),d},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c={},k.extend(c,b),k.extend(c,a)),c},programWithDepth:b.VM.programWithDepth,noop:b.VM.noop,compilerInfo:null};return function(c,e){e=e||{};var f,g,h=e.partial?e:b;e.partial||(f=e.helpers,g=e.partials);var i=a.call(d,h,c,f,g,e.data);return e.partial||b.VM.checkRevision(d.compilerInfo),i}}function f(a,b,c){var d=Array.prototype.slice.call(arguments,3),e=function(a,e){return e=e||{},b.apply(this,[a,e.data||c].concat(d))};return e.program=a,e.depth=d.length,e}function g(a,b,c){var d=function(a,d){return d=d||{},b(a,d.data||c)};return d.program=a,d.depth=0,d}function h(a,b,c,d,e,f){var g={partial:!0,helpers:d,partials:e,data:f};if(void 0===a)throw new l("The partial "+b+" could not be found");return a instanceof Function?a(c,g):void 0}function i(){return""}var j={},k=a,l=b,m=c.COMPILER_REVISION,n=c.REVISION_CHANGES;return j.checkRevision=d,j.template=e,j.programWithDepth=f,j.program=g,j.invokePartial=h,j.noop=i,j}(b,c,d),f=function(a,b,c,d,e){"use strict";var f,g=a,h=b,i=c,j=d,k=e,l=function(){var a=new g.HandlebarsEnvironment;return j.extend(a,g),a.SafeString=h,a.Exception=i,a.Utils=j,a.VM=k,a.template=function(b){return k.template(b,a)},a},m=l();return m.create=l,f=m}(d,a,c,b,e);return f}();this["wysihtml5"] = this["wysihtml5"] || {};
0 ignored issues
show
Best Practice introduced by
Comparing null to c.data.level using the != operator is not safe. Consider using !== instead.
Loading history...
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
Unused Code introduced by
The assignment to variable b seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable f seems to be never used. Consider removing it.
Loading history...
Coding Style Best Practice introduced by
By convention, constructors like h should be capitalized.
Loading history...
Unused Code introduced by
The variable b seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable f seems to not be initialized for all possible execution paths. Are you sure call handles undefined variables?
Loading history...
Bug introduced by
The variable p seems to be never initialized.
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
Bug introduced by
The variable g seems to not be initialized for all possible execution paths. Are you sure call handles undefined variables?
Loading history...
Best Practice introduced by
Comparing null to b using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility Naming Best Practice introduced by
The variable l already seems to be declared on line 13876. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
Coding Style Best Practice introduced by
By convention, constructors like l should be capitalized.
Loading history...
Best Practice introduced by
Comparing null to h using the != operator is not safe. Consider using !== instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Bug introduced by
The variable c does not seem to be initialized in case m(a) && a = a.call(this)...& "object" == typeof a on line 13876 is true. Are you sure this can never be the case?
Loading history...
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable f here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
13877
this["wysihtml5"]["tpl"] = this["wysihtml5"]["tpl"] || {};
13878
13879
this["wysihtml5"]["tpl"]["blockquote"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
13880
  this.compilerInfo = [4,'>= 1.0.0'];
13881
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
13882
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
13883
13884
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13885
  
13886
  var buffer = "", stack1;
13887
  buffer += "btn-"
13888
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13889
  return buffer;
13890
  }
13891
13892
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13893
  
13894
  
13895
  return " \n      <span class=\"fa fa-quote-left\"></span>\n    ";
13896
  }
13897
13898
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13899
  
13900
  
13901
  return "\n      <span class=\"glyphicon glyphicon-quote\"></span>\n    ";
13902
  }
13903
13904
  buffer += "<li>\n  <a class=\"btn ";
13905
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
13906
  if(stack1 || stack1 === 0) { buffer += stack1; }
13907
  buffer += " btn-default\" data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"blockquote\" data-wysihtml5-display-format-name=\"false\" tabindex=\"-1\">\n    ";
13908
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13909
  if(stack1 || stack1 === 0) { buffer += stack1; }
13910
  buffer += "\n  </a>\n</li>\n";
13911
  return buffer;
13912
  });
13913
13914
this["wysihtml5"]["tpl"]["color"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
13915
  this.compilerInfo = [4,'>= 1.0.0'];
13916
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
13917
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
13918
13919
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13920
  
13921
  var buffer = "", stack1;
13922
  buffer += "btn-"
13923
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
13924
  return buffer;
13925
  }
13926
13927
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
13928
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13929
  if(stack1 || stack1 === 0) { buffer += stack1; }
13930
  buffer += "\" data-toggle=\"dropdown\" tabindex=\"-1\">\n    <span class=\"current-color\">"
13931
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.black)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13932
    + "</span>\n    <b class=\"caret\"></b>\n  </a>\n  <ul class=\"dropdown-menu\">\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"black\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"black\">"
13933
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.black)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13934
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"silver\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"silver\">"
13935
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.silver)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13936
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"gray\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"gray\">"
13937
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.gray)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13938
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"maroon\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"maroon\">"
13939
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.maroon)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13940
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"red\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"red\">"
13941
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.red)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13942
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"purple\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"purple\">"
13943
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.purple)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13944
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"green\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"green\">"
13945
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.green)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13946
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"olive\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"olive\">"
13947
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.olive)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13948
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"navy\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"navy\">"
13949
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.navy)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13950
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"blue\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"blue\">"
13951
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.blue)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13952
    + "</a></li>\n    <li><div class=\"wysihtml5-colors\" data-wysihtml5-command-value=\"orange\"></div><a class=\"wysihtml5-colors-title\" data-wysihtml5-command=\"foreColor\" data-wysihtml5-command-value=\"orange\">"
13953
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.colours)),stack1 == null || stack1 === false ? stack1 : stack1.orange)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13954
    + "</a></li>\n  </ul>\n</li>\n";
13955
  return buffer;
13956
  });
13957
13958
this["wysihtml5"]["tpl"]["emphasis"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
13959
  this.compilerInfo = [4,'>= 1.0.0'];
13960
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
13961
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
13962
13963
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
13964
  
13965
  var buffer = "", stack1;
13966
  buffer += "btn-"
13967
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13968
  return buffer;
13969
  }
13970
13971
function program3(depth0,data) {
13972
  
13973
  var buffer = "", stack1;
13974
  buffer += "\n    <a class=\"btn ";
13975
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13976
  if(stack1 || stack1 === 0) { buffer += stack1; }
13977
  buffer += " btn-default\" data-wysihtml5-command=\"small\" title=\"CTRL+S\" tabindex=\"-1\">"
13978
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.small)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13979
    + "</a>\n    ";
13980
  return buffer;
13981
  }
13982
13983
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
13984
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13985
  if(stack1 || stack1 === 0) { buffer += stack1; }
13986
  buffer += " btn-default\" data-wysihtml5-command=\"bold\" title=\"CTRL+B\" tabindex=\"-1\">"
13987
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.bold)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13988
    + "</a>\n    <a class=\"btn ";
13989
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13990
  if(stack1 || stack1 === 0) { buffer += stack1; }
13991
  buffer += " btn-default\" data-wysihtml5-command=\"italic\" title=\"CTRL+I\" tabindex=\"-1\">"
13992
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.italic)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13993
    + "</a>\n    <a class=\"btn ";
13994
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13995
  if(stack1 || stack1 === 0) { buffer += stack1; }
13996
  buffer += " btn-default\" data-wysihtml5-command=\"underline\" title=\"CTRL+U\" tabindex=\"-1\">"
13997
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.underline)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
13998
    + "</a>\n    ";
13999
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.emphasis)),stack1 == null || stack1 === false ? stack1 : stack1.small), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14000
  if(stack1 || stack1 === 0) { buffer += stack1; }
14001
  buffer += "\n  </div>\n</li>\n";
14002
  return buffer;
14003
  });
14004
14005
this["wysihtml5"]["tpl"]["font-styles"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14006
  this.compilerInfo = [4,'>= 1.0.0'];
14007
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14008
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14009
14010
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14011
  
14012
  var buffer = "", stack1;
14013
  buffer += "btn-"
14014
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14015
  return buffer;
14016
  }
14017
14018
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14019
  
14020
  
14021
  return "\n      <span class=\"fa fa-font\"></span>\n    ";
14022
  }
14023
14024
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14025
  
14026
  
14027
  return "\n      <span class=\"glyphicon glyphicon-font\"></span>\n    ";
14028
  }
14029
14030
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
14031
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14032
  if(stack1 || stack1 === 0) { buffer += stack1; }
14033
  buffer += "\" data-toggle=\"dropdown\">\n    ";
14034
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14035
  if(stack1 || stack1 === 0) { buffer += stack1; }
14036
  buffer += "\n    <span class=\"current-font\">"
14037
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.normal)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14038
    + "</span>\n    <b class=\"caret\"></b>\n  </a>\n  <ul class=\"dropdown-menu\">\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"p\" tabindex=\"-1\">"
14039
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.normal)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14040
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h1\" tabindex=\"-1\">"
14041
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h1)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14042
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h2\" tabindex=\"-1\">"
14043
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h2)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14044
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h3\" tabindex=\"-1\">"
14045
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h3)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14046
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h4\" tabindex=\"-1\">"
14047
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h4)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14048
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h5\" tabindex=\"-1\">"
14049
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h5)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14050
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h6\" tabindex=\"-1\">"
14051
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.font_styles)),stack1 == null || stack1 === false ? stack1 : stack1.h6)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14052
    + "</a></li>\n  </ul>\n</li>\n";
14053
  return buffer;
14054
  });
14055
14056
this["wysihtml5"]["tpl"]["html"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14057
  this.compilerInfo = [4,'>= 1.0.0'];
14058
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14059
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14060
14061
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14062
  
14063
  var buffer = "", stack1;
14064
  buffer += "btn-"
14065
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14066
  return buffer;
14067
  }
14068
14069
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14070
  
14071
  
14072
  return "\n        <span class=\"fa fa-pencil\"></span>\n      ";
14073
  }
14074
14075
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14076
  
14077
  
14078
  return "\n        <span class=\"glyphicon glyphicon-pencil\"></span>\n      ";
14079
  }
14080
14081
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14082
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14083
  if(stack1 || stack1 === 0) { buffer += stack1; }
14084
  buffer += " btn-default\" data-wysihtml5-action=\"change_view\" title=\""
14085
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.html)),stack1 == null || stack1 === false ? stack1 : stack1.edit)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14086
    + "\" tabindex=\"-1\">\n      ";
14087
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14088
  if(stack1 || stack1 === 0) { buffer += stack1; }
14089
  buffer += "\n    </a>\n  </div>\n</li>\n";
14090
  return buffer;
14091
  });
14092
14093
this["wysihtml5"]["tpl"]["image"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14094
  this.compilerInfo = [4,'>= 1.0.0'];
14095
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14096
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14097
14098
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14099
  
14100
  
14101
  return "modal-sm";
14102
  }
14103
14104
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14105
  
14106
  var buffer = "", stack1;
14107
  buffer += "btn-"
14108
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14109
  return buffer;
14110
  }
14111
14112
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14113
  
14114
  
14115
  return "\n      <span class=\"fa fa-file-image-o\"></span>\n    ";
14116
  }
14117
14118
function program7(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14119
  
14120
  
14121
  return "\n      <span class=\"glyphicon glyphicon-picture\"></span>\n    ";
14122
  }
14123
14124
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-image-modal modal fade\" data-wysihtml5-dialog=\"insertImage\">\n    <div class=\"modal-dialog ";
14125
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.smallmodals), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14126
  if(stack1 || stack1 === 0) { buffer += stack1; }
14127
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14128
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14129
    + "</h3>\n        </div>\n        <div class=\"modal-body\">\n          <div class=\"form-group\">\n            <input value=\"http://\" class=\"bootstrap-wysihtml5-insert-image-url form-control\">\n          </div> \n        </div>\n        <div class=\"modal-footer\">\n          <a class=\"btn btn-default\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"cancel\" href=\"#\">"
14130
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.cancel)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14131
    + "</a>\n          <a class=\"btn btn-primary\" data-dismiss=\"modal\"  data-wysihtml5-dialog-action=\"save\" href=\"#\">"
14132
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14133
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14134
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14135
  if(stack1 || stack1 === 0) { buffer += stack1; }
14136
  buffer += " btn-default\" data-wysihtml5-command=\"insertImage\" title=\""
14137
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.image)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14138
    + "\" tabindex=\"-1\">\n    ";
14139
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14140
  if(stack1 || stack1 === 0) { buffer += stack1; }
14141
  buffer += "\n  </a>\n</li>\n";
14142
  return buffer;
14143
  });
14144
14145
this["wysihtml5"]["tpl"]["link"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14146
  this.compilerInfo = [4,'>= 1.0.0'];
14147
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14148
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14149
14150
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14151
  
14152
  
14153
  return "modal-sm";
14154
  }
14155
14156
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14157
  
14158
  var buffer = "", stack1;
14159
  buffer += "btn-"
14160
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14161
  return buffer;
14162
  }
14163
14164
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14165
  
14166
  
14167
  return "\n      <span class=\"fa fa-share-square-o\"></span>\n    ";
14168
  }
14169
14170
function program7(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14171
  
14172
  
14173
  return "\n      <span class=\"glyphicon glyphicon-share\"></span>\n    ";
14174
  }
14175
14176
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-link-modal modal fade\" data-wysihtml5-dialog=\"createLink\">\n    <div class=\"modal-dialog ";
14177
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.smallmodals), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14178
  if(stack1 || stack1 === 0) { buffer += stack1; }
14179
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14180
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14181
    + "</h3>\n        </div>\n        <div class=\"modal-body\">\n          <div class=\"form-group\">\n            <input value=\"http://\" class=\"bootstrap-wysihtml5-insert-link-url form-control\" data-wysihtml5-dialog-field=\"href\">\n          </div> \n          <div class=\"checkbox\">\n            <label> \n              <input type=\"checkbox\" class=\"bootstrap-wysihtml5-insert-link-target\" checked>"
14182
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.target)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14183
    + "\n            </label>\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <a class=\"btn btn-default\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"cancel\" href=\"#\">"
14184
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.cancel)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14185
    + "</a>\n          <a href=\"#\" class=\"btn btn-primary\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"save\">"
14186
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14187
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14188
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14189
  if(stack1 || stack1 === 0) { buffer += stack1; }
14190
  buffer += " btn-default\" data-wysihtml5-command=\"createLink\" title=\""
14191
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.link)),stack1 == null || stack1 === false ? stack1 : stack1.insert)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14192
    + "\" tabindex=\"-1\">\n    ";
14193
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14194
  if(stack1 || stack1 === 0) { buffer += stack1; }
14195
  buffer += "\n  </a>\n</li>\n";
14196
  return buffer;
14197
  });
14198
14199
this["wysihtml5"]["tpl"]["lists"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14200
  this.compilerInfo = [4,'>= 1.0.0'];
14201
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14202
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14203
14204
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14205
  
14206
  var buffer = "", stack1;
14207
  buffer += "btn-"
14208
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14209
  return buffer;
14210
  }
14211
14212
function program3(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14213
  
14214
  
14215
  return "\n      <span class=\"fa fa-list-ul\"></span>\n    ";
14216
  }
14217
14218
function program5(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14219
  
14220
  
14221
  return "\n      <span class=\"glyphicon glyphicon-list\"></span>\n    ";
14222
  }
14223
14224
function program7(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14225
  
14226
  
14227
  return "\n      <span class=\"fa fa-list-ol\"></span>\n    ";
14228
  }
14229
14230
function program9(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14231
  
14232
  
14233
  return "\n      <span class=\"glyphicon glyphicon-th-list\"></span>\n    ";
14234
  }
14235
14236
function program11(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14237
  
14238
  
14239
  return "\n      <span class=\"fa fa-outdent\"></span>\n    ";
14240
  }
14241
14242
function program13(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14243
  
14244
  
14245
  return "\n      <span class=\"glyphicon glyphicon-indent-right\"></span>\n    ";
14246
  }
14247
14248
function program15(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14249
  
14250
  
14251
  return "\n      <span class=\"fa fa-indent\"></span>\n    ";
14252
  }
14253
14254
function program17(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter depth0 is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14255
  
14256
  
14257
  return "\n      <span class=\"glyphicon glyphicon-indent-left\"></span>\n    ";
14258
  }
14259
14260
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14261
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
Best Practice introduced by
Comparing stack1 to null using the == operator is not safe. Consider using === instead.
Loading history...
14262
  if(stack1 || stack1 === 0) { buffer += stack1; }
14263
  buffer += " btn-default\" data-wysihtml5-command=\"insertUnorderedList\" title=\""
14264
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.unordered)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14265
    + "\" tabindex=\"-1\">\n    ";
14266
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14267
  if(stack1 || stack1 === 0) { buffer += stack1; }
14268
  buffer += "\n    </a>\n    <a class=\"btn ";
14269
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14270
  if(stack1 || stack1 === 0) { buffer += stack1; }
14271
  buffer += " btn-default\" data-wysihtml5-command=\"insertOrderedList\" title=\""
14272
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.ordered)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14273
    + "\" tabindex=\"-1\">\n    ";
14274
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(9, program9, data),fn:self.program(7, program7, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14275
  if(stack1 || stack1 === 0) { buffer += stack1; }
14276
  buffer += "\n    </a>\n    <a class=\"btn ";
14277
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14278
  if(stack1 || stack1 === 0) { buffer += stack1; }
14279
  buffer += " btn-default\" data-wysihtml5-command=\"Outdent\" title=\""
14280
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.outdent)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14281
    + "\" tabindex=\"-1\">\n    ";
14282
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(13, program13, data),fn:self.program(11, program11, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14283
  if(stack1 || stack1 === 0) { buffer += stack1; }
14284
  buffer += "\n    </a>\n    <a class=\"btn ";
14285
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14286
  if(stack1 || stack1 === 0) { buffer += stack1; }
14287
  buffer += " btn-default\" data-wysihtml5-command=\"Indent\" title=\""
14288
    + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.locale)),stack1 == null || stack1 === false ? stack1 : stack1.lists)),stack1 == null || stack1 === false ? stack1 : stack1.indent)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14289
    + "\" tabindex=\"-1\">\n    ";
14290
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.fa), {hash:{},inverse:self.program(17, program17, data),fn:self.program(15, program15, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

The sequence or comma operator allows the inclusion of multiple expressions where only is permitted. The result of the sequence is the value of the last expression.

This operator is most often used in for statements.

Used in another places it can make code hard to read, especially when people do not realize it even exists as a seperate operator.

This check looks for usage of the sequence operator in locations where it is not necessary and could be replaced by a series of expressions or statements.

var a,b,c;

a = 1, b = 1,  c= 3;

could just as well be written as:

var a,b,c;

a = 1;
b = 1;
c = 3;

To learn more about the sequence operator, please refer to the MDN.

Loading history...
14291
  if(stack1 || stack1 === 0) { buffer += stack1; }
14292
  buffer += "\n    </a>\n  </div>\n</li>\n";
14293
  return buffer;
14294
  });/* jshint expr: true */
14295
(function (factory) {
14296
    if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
14297
        // AMD. Register as an anonymous module.
14298
        define('bootstrap.wysihtml5', ['jquery', 'wysihtml5', 'bootstrap', 'bootstrap.wysihtml5.templates', 'bootstrap.wysihtml5.commands'], factory);
14299
    } else {
14300
        // Browser globals
14301
        factory(jQuery, wysihtml5);
14302
    }
14303 View Code Duplication
}(function ($, wysihtml5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14304
14305
var bsWysihtml5 = function($, wysihtml5) {
14306
  'use strict';
14307
14308
  var templates = function(key, locale, options) {
14309
    if(wysihtml5.tpl[key]) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if wysihtml5.tpl.key is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
14310
      return wysihtml5.tpl[key]({locale: locale, options: options});
14311
    }
14312
  };
14313
14314
  var Wysihtml5 = function(el, options) {
14315
    this.el = el;
14316
    var toolbarOpts = $.extend(true, {}, defaultOptions, options);
14317
    for(var t in toolbarOpts.customTemplates) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
14318
      wysihtml5.tpl[t] = toolbarOpts.customTemplates[t];
14319
    }
14320
    this.toolbar = this.createToolbar(el, toolbarOpts);
14321
    this.editor =  this.createEditor(toolbarOpts);
14322
  };
14323
14324
  Wysihtml5.prototype = {
14325
14326
    constructor: Wysihtml5,
14327
14328
    createEditor: function(options) {
14329
      options = options || {};
14330
14331
      // Add the toolbar to a clone of the options object so multiple instances
14332
      // of the WYISYWG don't break because 'toolbar' is already defined
14333
      options = $.extend(true, {}, options);
14334
      options.toolbar = this.toolbar[0];
14335
14336
      var editor = new wysihtml5.Editor(this.el[0], options);
14337
14338
      // #30 - body is in IE 10 not created by default, which leads to nullpointer
14339
      // 2014/02/13 - adapted to wysihtml5-0.4, does not work in IE
14340
      if(editor.composer.editableArea.contentDocument) {
14341
        this.addMoreShortcuts(editor, editor.composer.editableArea.contentDocument.body || editor.composer.editableArea.contentDocument, options.shortcuts);
14342
      } else {
14343
        this.addMoreShortcuts(editor, editor.composer.editableArea, options.shortcuts);    
14344
      }
14345
      
14346
14347
      if(options && options.events) {
14348
        for(var eventName in options.events) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
14349
          editor.on(eventName, options.events[eventName]);
14350
        }
14351
      }
14352
      
14353
      editor.on('beforeload', this.syncBootstrapDialogEvents);
14354
      //syncBootstrapDialogEvents();
14355
      return editor;
14356
    },
14357
14358
    //sync wysihtml5 events for dialogs with bootstrap events
14359
    syncBootstrapDialogEvents: function() {
14360
      var editor = this;
14361
      $.map(this.toolbar.commandMapping, function(value, index) {
0 ignored issues
show
Unused Code introduced by
The parameter index is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14362
        return [value];
14363
      }).filter(function(commandObj, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter idx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter arr is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14364
        return commandObj.dialog;
14365
      }).map(function(commandObj, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter idx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter arr is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14366
        return commandObj.dialog;
14367
      }).forEach(function(dialog, idx, arr) {
0 ignored issues
show
Unused Code introduced by
The parameter idx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter arr is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14368
        dialog.on('show', function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14369
          $(this.container).modal('show');
14370
        });
14371
        dialog.on('hide', function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14372
          $(this.container).modal('hide');
14373
          editor.composer.focus();
14374
        });
14375
        $(dialog.container).on('shown.bs.modal', function () {
14376
          $(this).find('input, select, textarea').first().focus();
14377
        });
14378
      });
14379
    },
14380
14381
    createToolbar: function(el, options) {
14382
      var self = this;
14383
      var toolbar = $('<ul/>', {
14384
        'class' : 'wysihtml5-toolbar',
14385
        'style': 'display:none'
14386
      });
14387
      var culture = options.locale || defaultOptions.locale || 'en';
14388
      if(!locale.hasOwnProperty(culture)) {
14389
        console.debug('Locale \'' + culture + '\' not found. Available locales are: ' + Object.keys(locale) + '. Falling back to \'en\'.');
14390
        culture = 'en';
14391
      }
14392
      var localeObject = $.extend(true, {}, locale.en, locale[culture]);
14393
      for(var key in options.toolbar) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
14394
        if(options.toolbar[key]) {
14395
          toolbar.append(templates(key, localeObject, options));
14396
14397
          if(key === 'html') {
14398
            this.initHtml(toolbar);
14399
          }
14400
14401
        }
14402
      }
14403
14404
      toolbar.find('a[data-wysihtml5-command="formatBlock"]').click(function(e) {
14405
        var target = e.delegateTarget || e.target || e.srcElement,
14406
            el = $(target),
14407
            showformat = el.data('wysihtml5-display-format-name'),
14408
            formatname = el.data('wysihtml5-format-name') || el.html();
14409
        if(showformat === undefined || showformat === 'true') {
14410
          self.toolbar.find('.current-font').text(formatname);
14411
        }
14412
      });
14413
14414
      toolbar.find('a[data-wysihtml5-command="foreColor"]').click(function(e) {
14415
        var target = e.target || e.srcElement;
14416
        var el = $(target);
14417
        self.toolbar.find('.current-color').text(el.html());
14418
      });
14419
14420
      this.el.before(toolbar);
14421
14422
      return toolbar;
14423
    },
14424
14425
    initHtml: function(toolbar) {
14426
      var changeViewSelector = 'a[data-wysihtml5-action="change_view"]';
14427
      toolbar.find(changeViewSelector).click(function(e) {
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
14428
        toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
14429
      });
14430
    },
14431
14432
    addMoreShortcuts: function(editor, el, shortcuts) {
14433
      /* some additional shortcuts */
14434
      wysihtml5.dom.observe(el, 'keydown', function(event) {
14435
        var keyCode  = event.keyCode,
14436
            command  = shortcuts[keyCode];
14437
        if ((event.ctrlKey || event.metaKey || event.altKey) && command && wysihtml5.commands[command]) {
14438
14439
          var commandObj = editor.toolbar.commandMapping[command + ':null'];
14440
          if (commandObj && commandObj.dialog && !commandObj.state) {
14441
            commandObj.dialog.show();
14442
          } else {
14443
            wysihtml5.commands[command].exec(editor.composer, command);
14444
          }
14445
          event.preventDefault();
14446
        }
14447
      });
14448
    }
14449
  };
14450
14451
  // these define our public api
14452
  var methods = {
14453
    resetDefaults: function() {
14454
      $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
14455
    },
14456
    bypassDefaults: function(options) {
14457
      return this.each(function () {
14458
        var $this = $(this);
14459
        $this.data('wysihtml5', new Wysihtml5($this, options));
14460
      });
14461
    },
14462
    shallowExtend: function (options) {
14463
      var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
14464
      var that = this;
14465
      return methods.bypassDefaults.apply(that, [settings]);
14466
    },
14467
    deepExtend: function(options) {
14468
      var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
14469
      var that = this;
14470
      return methods.bypassDefaults.apply(that, [settings]);
14471
    },
14472
    init: function(options) {
14473
      var that = this;
14474
      return methods.shallowExtend.apply(that, [options]);
14475
    }
14476
  };
14477
14478
  $.fn.wysihtml5 = function ( method ) {
14479
    if ( methods[method] ) {
14480
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
14481
    } else if ( typeof method === 'object' || ! method ) {
14482
      return methods.init.apply( this, arguments );
14483
    } else {
14484
      $.error( 'Method ' +  method + ' does not exist on jQuery.wysihtml5' );
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
14485
    }    
14486
  };
14487
14488
  $.fn.wysihtml5.Constructor = Wysihtml5;
14489
14490
  var defaultOptions = $.fn.wysihtml5.defaultOptions = {
14491
    toolbar: {
14492
      'font-styles': true,
14493
      'color': false,
14494
      'emphasis': {
14495
        'small': true
14496
      },
14497
      'blockquote': true,
14498
      'lists': true,
14499
      'html': false,
14500
      'link': true,
14501
      'image': true,
14502
      'smallmodals': false
14503
    },
14504
    parserRules: {
14505
      classes: {
14506
        'wysiwyg-color-silver' : 1,
14507
        'wysiwyg-color-gray' : 1,
14508
        'wysiwyg-color-white' : 1,
14509
        'wysiwyg-color-maroon' : 1,
14510
        'wysiwyg-color-red' : 1,
14511
        'wysiwyg-color-purple' : 1,
14512
        'wysiwyg-color-fuchsia' : 1,
14513
        'wysiwyg-color-green' : 1,
14514
        'wysiwyg-color-lime' : 1,
14515
        'wysiwyg-color-olive' : 1,
14516
        'wysiwyg-color-yellow' : 1,
14517
        'wysiwyg-color-navy' : 1,
14518
        'wysiwyg-color-blue' : 1,
14519
        'wysiwyg-color-teal' : 1,
14520
        'wysiwyg-color-aqua' : 1,
14521
        'wysiwyg-color-orange' : 1
14522
      },
14523
      tags: {
14524
        'b':  {},
14525
        'i':  {},
14526
        'strong': {},
14527
        'em': {},
14528
        'p': {},
14529
        'br': {},
14530
        'ol': {},
14531
        'ul': {},
14532
        'li': {},
14533
        'h1': {},
14534
        'h2': {},
14535
        'h3': {},
14536
        'h4': {},
14537
        'h5': {},
14538
        'h6': {},
14539
        'blockquote': {},
14540
        'u': 1,
14541
        'img': {
14542
          'check_attributes': {
14543
            'width': 'numbers',
14544
            'alt': 'alt',
14545
            'src': 'url',
14546
            'height': 'numbers'
14547
          }
14548
        },
14549
        'a':  {
14550
          check_attributes: {
14551
            'href': 'url' // important to avoid XSS
14552
          },
14553
          'set_attributes': {
14554
            'target': '_blank',
14555
            'rel': 'nofollow'
14556
          }
14557
        },
14558
        'span': 1,
14559
        'div': 1,
14560
        'small': 1,
14561
        // to allow save and edit files with code tag hacks
14562
        'code': 1,
14563
        'pre': 1
14564
      }
14565
    },
14566
    locale: 'en',
14567
    shortcuts: {
14568
      '83': 'small'     // S
14569
    }
14570
    
14571
  };
14572
14573
  if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
14574
    $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
14575
  }
14576
14577
  var locale = $.fn.wysihtml5.locale = {};
14578
};
14579
bsWysihtml5($, wysihtml5);
14580
}));
14581
(function(wysihtml5) {
14582
  wysihtml5.commands.small = {
14583
    exec: function(composer, command) {
14584
      return wysihtml5.commands.formatInline.exec(composer, command, "small");
14585
    },
14586
14587
    state: function(composer, command) {
14588
      return wysihtml5.commands.formatInline.state(composer, command, "small");
14589
    }
14590
  };
14591
})(wysihtml5);
14592
14593
/**
14594
 * English translation for bootstrap-wysihtml5
14595
 */
14596
(function (factory) {
14597
    if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
14598
        // AMD. Register as an anonymous module.
14599
        define('bootstrap.wysihtml5.en-US', ['jquery', 'bootstrap.wysihtml5'], factory);
14600
    } else {
14601
        // Browser globals
14602
        factory(jQuery);
14603
    }
14604 View Code Duplication
}(function ($) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14605
  $.fn.wysihtml5.locale.en = $.fn.wysihtml5.locale['en-US'] = {
14606
    font_styles: {
14607
      normal: 'Normal text',
14608
      h1: 'Heading 1',
14609
      h2: 'Heading 2',
14610
      h3: 'Heading 3',
14611
      h4: 'Heading 4',
14612
      h5: 'Heading 5',
14613
      h6: 'Heading 6'
14614
    },
14615
    emphasis: {
14616
      bold: 'Bold',
14617
      italic: 'Italic',
14618
      underline: 'Underline',
14619
      small: 'Small'
14620
    },
14621
    lists: {
14622
      unordered: 'Unordered list',
14623
      ordered: 'Ordered list',
14624
      outdent: 'Outdent',
14625
      indent: 'Indent'
14626
    },
14627
    link: {
14628
      insert: 'Insert link',
14629
      cancel: 'Cancel',
14630
      target: 'Open link in new window'
14631
    },
14632
    image: {
14633
      insert: 'Insert image',
14634
      cancel: 'Cancel'
14635
    },
14636
    html: {
14637
      edit: 'Edit HTML'
14638
    },
14639
    colours: {
14640
      black: 'Black',
14641
      silver: 'Silver',
14642
      gray: 'Grey',
14643
      maroon: 'Maroon',
14644
      red: 'Red',
14645
      purple: 'Purple',
14646
      green: 'Green',
14647
      olive: 'Olive',
14648
      navy: 'Navy',
14649
      blue: 'Blue',
14650
      orange: 'Orange'
14651
    }
14652
  };
14653
}));
14654