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

Complexity

Total Complexity 3041
Complexity/F 2.53

Size

Lines of Code 15097
Function Count 1204

Duplication

Duplicated Lines 14598
Ratio 96.69 %

Importance

Changes 0
Metric Value
wmc 3041
eloc 8629
c 0
b 0
f 0
dl 14598
loc 15097
rs 0.8
mnd 1837
bc 1837
fnc 1204
bpm 1.5257
cpm 2.5257
noi 473

216 Functions

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

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/amd/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 View Code Duplication
define('wysihtml5', function (require, exports, module) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2
            var $     = require('jquery'),
0 ignored issues
show
Unused Code introduced by
The variable $ seems to be never used. Consider removing it.
Loading history...
3
                rangy = require('rangy');
4
5
// TODO: in future try to replace most inline compability checks with polyfills for code readability 
6
7
// element.textContent polyfill.
8
// Unsupporting browsers: IE8
9
10
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...
11
	(function() {
12
		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...
13
		Object.defineProperty(Element.prototype, "textContent",
14
			{
15
				get: function() {
16
					return innerText.get.call(this);
17
				},
18
				set: function(s) {
19
					return innerText.set.call(this, s);
20
				}
21
			}
22
		);
23
	})();
24
}
25
26
// isArray polyfill for ie8
27
if(!Array.isArray) {
28
  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...
29
    return Object.prototype.toString.call(arg) === '[object Array]';
30
  };
31
};/**
32
 * @license wysihtml5x v0.4.13
33
 * https://github.com/Edicy/wysihtml5
34
 *
35
 * Author: Christopher Blum (https://github.com/tiff)
36
 * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
37
 *
38
 * Copyright (C) 2012 XING AG
39
 * Licensed under the MIT license (MIT)
40
 *
41
 */
42
var wysihtml5 = {
43
  version: "0.4.13",
44
45
  // namespaces
46
  commands:   {},
47
  dom:        {},
48
  quirks:     {},
49
  toolbar:    {},
50
  lang:       {},
51
  selection:  {},
52
  views:      {},
53
54
  INVISIBLE_SPACE: "\uFEFF",
55
56
  EMPTY_FUNCTION: function() {},
57
58
  ELEMENT_NODE: 1,
59
  TEXT_NODE:    3,
60
61
  BACKSPACE_KEY:  8,
62
  ENTER_KEY:      13,
63
  ESCAPE_KEY:     27,
64
  SPACE_KEY:      32,
65
  DELETE_KEY:     46
66
};
67
;/**
68
 * Rangy, a cross-browser JavaScript range and selection library
69
 * http://code.google.com/p/rangy/
70
 *
71
 * Copyright 2014, Tim Down
72
 * Licensed under the MIT license.
73
 * Version: 1.3alpha.20140804
74
 * Build date: 4 August 2014
75
 */
76
77
(function(factory, global) {
78
    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...
79
        // AMD. Register as an anonymous module.
80
        define(factory);
81
/*
82
    TODO: look into this properly.
83
    
84
    } else if (typeof exports == "object") {
85
        // Node/CommonJS style for Browserify
86
        module.exports = factory;
87
*/
88
    } else {
89
        // No AMD or CommonJS support so we place Rangy in a global variable
90
        global.rangy = factory();
91
    }
92
})(function() {
93
94
    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
95
96
    // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
97
    // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
98
    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
99
        "commonAncestorContainer"];
100
101
    // Minimal set of methods required for DOM Level 2 Range compliance
102
    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
103
        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
104
        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
105
106
    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
107
108
    // Subset of TextRange's full set of methods that we're interested in
109
    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
110
        "setEndPoint", "getBoundingClientRect"];
111
112
    /*----------------------------------------------------------------------------------------------------------------*/
113
114
    // Trio of functions taken from Peter Michaux's article:
115
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
116
    function isHostMethod(o, p) {
117
        var t = typeof o[p];
118
        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
119
    }
120
121
    function isHostObject(o, p) {
122
        return !!(typeof o[p] == OBJECT && o[p]);
123
    }
124
125
    function isHostProperty(o, p) {
126
        return typeof o[p] != UNDEFINED;
127
    }
128
129
    // Creates a convenience function to save verbose repeated calls to tests functions
130
    function createMultiplePropertyTest(testFunc) {
131
        return function(o, props) {
132
            var i = props.length;
133
            while (i--) {
134
                if (!testFunc(o, props[i])) {
135
                    return false;
136
                }
137
            }
138
            return true;
139
        };
140
    }
141
142
    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
143
    var areHostMethods = createMultiplePropertyTest(isHostMethod);
144
    var areHostObjects = createMultiplePropertyTest(isHostObject);
145
    var areHostProperties = createMultiplePropertyTest(isHostProperty);
146
147
    function isTextRange(range) {
148
        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
149
    }
150
151
    function getBody(doc) {
152
        return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
153
    }
154
155
    var modules = {};
156
157
    var api = {
158
        version: "1.3alpha.20140804",
159
        initialized: false,
160
        supported: true,
161
162
        util: {
163
            isHostMethod: isHostMethod,
164
            isHostObject: isHostObject,
165
            isHostProperty: isHostProperty,
166
            areHostMethods: areHostMethods,
167
            areHostObjects: areHostObjects,
168
            areHostProperties: areHostProperties,
169
            isTextRange: isTextRange,
170
            getBody: getBody
171
        },
172
173
        features: {},
174
175
        modules: modules,
176
        config: {
177
            alertOnFail: true,
178
            alertOnWarn: false,
179
            preferTextRange: false,
180
            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...
181
        }
182
    };
183
184
    function consoleLog(msg) {
185
        if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
186
            window.console.log(msg);
187
        }
188
    }
189
190
    function alertOrLog(msg, shouldAlert) {
191
        if (shouldAlert) {
192
            window.alert(msg);
193
        } else  {
194
            consoleLog(msg);
195
        }
196
    }
197
198
    function fail(reason) {
199
        api.initialized = true;
200
        api.supported = false;
201
        alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail);
202
    }
203
204
    api.fail = fail;
205
206
    function warn(msg) {
207
        alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
208
    }
209
210
    api.warn = warn;
211
212
    // Add utility extend() method
213
    if ({}.hasOwnProperty) {
214
        api.util.extend = function(obj, props, deep) {
215
            var o, p;
216
            for (var i in props) {
217
                if (props.hasOwnProperty(i)) {
218
                    o = obj[i];
219
                    p = props[i];
220
                    if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
221
                        api.util.extend(o, p, true);
222
                    }
223
                    obj[i] = p;
224
                }
225
            }
226
            // Special case for toString, which does not show up in for...in loops in IE <= 8
227
            if (props.hasOwnProperty("toString")) {
228
                obj.toString = props.toString;
229
            }
230
            return obj;
231
        };
232
    } else {
233
        fail("hasOwnProperty not supported");
234
    }
235
236
    // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
237
    (function() {
238
        var el = document.createElement("div");
239
        el.appendChild(document.createElement("span"));
240
        var slice = [].slice;
241
        var toArray;
242
        try {
243
            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...
244
                toArray = function(arrayLike) {
245
                    return slice.call(arrayLike, 0);
246
                };
247
            }
248
        } 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...
249
250
        if (!toArray) {
251
            toArray = function(arrayLike) {
252
                var arr = [];
253
                for (var i = 0, len = arrayLike.length; i < len; ++i) {
254
                    arr[i] = arrayLike[i];
255
                }
256
                return arr;
257
            };
258
        }
259
260
        api.util.toArray = toArray;
261
    })();
262
263
264
    // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
265
    // normalization of event properties
266
    var addListener;
267
    if (isHostMethod(document, "addEventListener")) {
268
        addListener = function(obj, eventType, listener) {
269
            obj.addEventListener(eventType, listener, false);
270
        };
271
    } else if (isHostMethod(document, "attachEvent")) {
272
        addListener = function(obj, eventType, listener) {
273
            obj.attachEvent("on" + eventType, listener);
274
        };
275
    } else {
276
        fail("Document does not have required addEventListener or attachEvent method");
277
    }
278
279
    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...
280
281
    var initListeners = [];
282
283
    function getErrorDesc(ex) {
284
        return ex.message || ex.description || String(ex);
285
    }
286
287
    // Initialization
288
    function init() {
289
        if (api.initialized) {
290
            return;
291
        }
292
        var testRange;
293
        var implementsDomRange = false, implementsTextRange = false;
294
295
        // First, perform basic feature tests
296
297
        if (isHostMethod(document, "createRange")) {
298
            testRange = document.createRange();
299
            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
300
                implementsDomRange = true;
301
            }
302
        }
303
304
        var body = getBody(document);
305
        if (!body || body.nodeName.toLowerCase() != "body") {
306
            fail("No body element found");
307
            return;
308
        }
309
310
        if (body && isHostMethod(body, "createTextRange")) {
311
            testRange = body.createTextRange();
312
            if (isTextRange(testRange)) {
313
                implementsTextRange = true;
314
            }
315
        }
316
317
        if (!implementsDomRange && !implementsTextRange) {
318
            fail("Neither Range nor TextRange are available");
319
            return;
320
        }
321
322
        api.initialized = true;
323
        api.features = {
324
            implementsDomRange: implementsDomRange,
325
            implementsTextRange: implementsTextRange
326
        };
327
328
        // Initialize modules
329
        var module, errorMessage;
330
        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...
331
            if ( (module = modules[moduleName]) instanceof Module ) {
332
                module.init(module, api);
333
            }
334
        }
335
336
        // Call init listeners
337
        for (var i = 0, len = initListeners.length; i < len; ++i) {
338
            try {
339
                initListeners[i](api);
340
            } catch (ex) {
341
                errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
342
                consoleLog(errorMessage);
343
            }
344
        }
345
    }
346
347
    // Allow external scripts to initialize this library in case it's loaded after the document has loaded
348
    api.init = init;
349
350
    // Execute listener immediately if already initialized
351
    api.addInitListener = function(listener) {
352
        if (api.initialized) {
353
            listener(api);
354
        } else {
355
            initListeners.push(listener);
356
        }
357
    };
358
359
    var shimListeners = [];
360
361
    api.addShimListener = function(listener) {
362
        shimListeners.push(listener);
363
    };
364
365
    function shim(win) {
366
        win = win || window;
367
        init();
368
369
        // Notify listeners
370
        for (var i = 0, len = shimListeners.length; i < len; ++i) {
371
            shimListeners[i](win);
372
        }
373
    }
374
375
    api.shim = api.createMissingNativeApi = shim;
376
377
    function Module(name, dependencies, initializer) {
378
        this.name = name;
379
        this.dependencies = dependencies;
380
        this.initialized = false;
381
        this.supported = false;
382
        this.initializer = initializer;
383
    }
384
385
    Module.prototype = {
386
        init: function() {
387
            var requiredModuleNames = this.dependencies || [];
388
            for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
389
                moduleName = requiredModuleNames[i];
390
391
                requiredModule = modules[moduleName];
392
                if (!requiredModule || !(requiredModule instanceof Module)) {
393
                    throw new Error("required module '" + moduleName + "' not found");
394
                }
395
396
                requiredModule.init();
397
398
                if (!requiredModule.supported) {
399
                    throw new Error("required module '" + moduleName + "' not supported");
400
                }
401
            }
402
            
403
            // Now run initializer
404
            this.initializer(this);
405
        },
406
        
407
        fail: function(reason) {
408
            this.initialized = true;
409
            this.supported = false;
410
            throw new Error("Module '" + this.name + "' failed to load: " + reason);
411
        },
412
413
        warn: function(msg) {
414
            api.warn("Module " + this.name + ": " + msg);
415
        },
416
417
        deprecationNotice: function(deprecated, replacement) {
418
            api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " +
419
                replacement + " instead");
420
        },
421
422
        createError: function(msg) {
423
            return new Error("Error in Rangy " + this.name + " module: " + msg);
424
        }
425
    };
426
    
427
    function createModule(isCore, name, dependencies, initFunc) {
428
        var newModule = new Module(name, dependencies, function(module) {
429
            if (!module.initialized) {
430
                module.initialized = true;
431
                try {
432
                    initFunc(api, module);
433
                    module.supported = true;
434
                } catch (ex) {
435
                    var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
436
                    consoleLog(errorMessage);
437
                }
438
            }
439
        });
440
        modules[name] = newModule;
441
    }
442
443
    api.createModule = function(name) {
444
        // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
445
        var initFunc, dependencies;
446
        if (arguments.length == 2) {
447
            initFunc = arguments[1];
448
            dependencies = [];
449
        } else {
450
            initFunc = arguments[2];
451
            dependencies = arguments[1];
452
        }
453
454
        var module = createModule(false, name, dependencies, initFunc);
455
456
        // Initialize the module immediately if the core is already initialized
457
        if (api.initialized) {
458
            module.init();
459
        }
460
    };
461
462
    api.createCoreModule = function(name, dependencies, initFunc) {
463
        createModule(true, name, dependencies, initFunc);
464
    };
465
466
    /*----------------------------------------------------------------------------------------------------------------*/
467
468
    // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
469
470
    function RangePrototype() {}
471
    api.RangePrototype = RangePrototype;
472
    api.rangePrototype = new RangePrototype();
473
474
    function SelectionPrototype() {}
475
    api.selectionPrototype = new SelectionPrototype();
476
477
    /*----------------------------------------------------------------------------------------------------------------*/
478
479
    // Wait for document to load before running tests
480
481
    var docReady = false;
482
483
    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...
484
        if (!docReady) {
485
            docReady = true;
486
            if (!api.initialized && api.config.autoInitialize) {
487
                init();
488
            }
489
        }
490
    };
491
492
    // Test whether we have window and document objects that we will need
493
    if (typeof window == UNDEFINED) {
494
        fail("No window 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
    if (typeof document == UNDEFINED) {
498
        fail("No document found");
499
        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...
500
    }
501
502
    if (isHostMethod(document, "addEventListener")) {
503
        document.addEventListener("DOMContentLoaded", loadHandler, false);
504
    }
505
506
    // Add a fallback in case the DOMContentLoaded event isn't supported
507
    addListener(window, "load", loadHandler);
508
509
    /*----------------------------------------------------------------------------------------------------------------*/
510
    
511
    // DOM utility methods used by Rangy
512
    api.createCoreModule("DomUtil", [], function(api, module) {
513
        var UNDEF = "undefined";
514
        var util = api.util;
515
516
        // Perform feature tests
517
        if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
518
            module.fail("document missing a Node creation method");
519
        }
520
521
        if (!util.isHostMethod(document, "getElementsByTagName")) {
522
            module.fail("document missing getElementsByTagName method");
523
        }
524
525
        var el = document.createElement("div");
526
        if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
527
                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
528
            module.fail("Incomplete Element implementation");
529
        }
530
531
        // innerHTML is required for Range's createContextualFragment method
532
        if (!util.isHostProperty(el, "innerHTML")) {
533
            module.fail("Element is missing innerHTML property");
534
        }
535
536
        var textNode = document.createTextNode("test");
537
        if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
538
                !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
539
                !util.areHostProperties(textNode, ["data"]))) {
540
            module.fail("Incomplete Text Node implementation");
541
        }
542
543
        /*----------------------------------------------------------------------------------------------------------------*/
544
545
        // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
546
        // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
547
        // contains just the document as a single element and the value searched for is the document.
548
        var arrayContains = /*Array.prototype.indexOf ?
549
            function(arr, val) {
550
                return arr.indexOf(val) > -1;
551
            }:*/
552
553
            function(arr, val) {
554
                var i = arr.length;
555
                while (i--) {
556
                    if (arr[i] === val) {
557
                        return true;
558
                    }
559
                }
560
                return false;
561
            };
562
563
        // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
564
        function isHtmlNamespace(node) {
565
            var ns;
566
            return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
567
        }
568
569
        function parentElement(node) {
570
            var parent = node.parentNode;
571
            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...
572
        }
573
574
        function getNodeIndex(node) {
575
            var i = 0;
576
            while( (node = node.previousSibling) ) {
577
                ++i;
578
            }
579
            return i;
580
        }
581
582
        function getNodeLength(node) {
583
            switch (node.nodeType) {
584
                case 7:
585
                case 10:
586
                    return 0;
587
                case 3:
588
                case 8:
589
                    return node.length;
590
                default:
591
                    return node.childNodes.length;
592
            }
593
        }
594
595
        function getCommonAncestor(node1, node2) {
596
            var ancestors = [], n;
597
            for (n = node1; n; n = n.parentNode) {
598
                ancestors.push(n);
599
            }
600
601
            for (n = node2; n; n = n.parentNode) {
602
                if (arrayContains(ancestors, n)) {
603
                    return n;
604
                }
605
            }
606
607
            return null;
608
        }
609
610
        function isAncestorOf(ancestor, descendant, selfIsAncestor) {
611
            var n = selfIsAncestor ? descendant : descendant.parentNode;
612
            while (n) {
613
                if (n === ancestor) {
614
                    return true;
615
                } 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...
616
                    n = n.parentNode;
617
                }
618
            }
619
            return false;
620
        }
621
622
        function isOrIsAncestorOf(ancestor, descendant) {
623
            return isAncestorOf(ancestor, descendant, true);
624
        }
625
626
        function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
627
            var p, n = selfIsAncestor ? node : node.parentNode;
628
            while (n) {
629
                p = n.parentNode;
630
                if (p === ancestor) {
631
                    return n;
632
                }
633
                n = p;
634
            }
635
            return null;
636
        }
637
638
        function isCharacterDataNode(node) {
639
            var t = node.nodeType;
640
            return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
641
        }
642
643
        function isTextOrCommentNode(node) {
644
            if (!node) {
645
                return false;
646
            }
647
            var t = node.nodeType;
648
            return t == 3 || t == 8 ; // Text or Comment
649
        }
650
651
        function insertAfter(node, precedingNode) {
652
            var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
653
            if (nextNode) {
654
                parent.insertBefore(node, nextNode);
655
            } else {
656
                parent.appendChild(node);
657
            }
658
            return node;
659
        }
660
661
        // Note that we cannot use splitText() because it is bugridden in IE 9.
662
        function splitDataNode(node, index, positionsToPreserve) {
663
            var newNode = node.cloneNode(false);
664
            newNode.deleteData(0, index);
665
            node.deleteData(index, node.length - index);
666
            insertAfter(newNode, node);
667
668
            // Preserve positions
669
            if (positionsToPreserve) {
670
                for (var i = 0, position; position = positionsToPreserve[i++]; ) {
671
                    // Handle case where position was inside the portion of node after the split point
672
                    if (position.node == node && position.offset > index) {
673
                        position.node = newNode;
674
                        position.offset -= index;
675
                    }
676
                    // Handle the case where the position is a node offset within node's parent
677
                    else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
678
                        ++position.offset;
679
                    }
680
                }
681
            }
682
            return newNode;
683
        }
684
685
        function getDocument(node) {
686
            if (node.nodeType == 9) {
687
                return node;
688
            } else if (typeof node.ownerDocument != UNDEF) {
689
                return node.ownerDocument;
690
            } else if (typeof node.document != UNDEF) {
691
                return node.document;
692
            } else if (node.parentNode) {
693
                return getDocument(node.parentNode);
694
            } else {
695
                throw module.createError("getDocument: no document found for node");
696
            }
697
        }
698
699
        function getWindow(node) {
700
            var doc = getDocument(node);
701
            if (typeof doc.defaultView != UNDEF) {
702
                return doc.defaultView;
703
            } else if (typeof doc.parentWindow != UNDEF) {
704
                return doc.parentWindow;
705
            } else {
706
                throw module.createError("Cannot get a window object for node");
707
            }
708
        }
709
710
        function getIframeDocument(iframeEl) {
711
            if (typeof iframeEl.contentDocument != UNDEF) {
712
                return iframeEl.contentDocument;
713
            } else if (typeof iframeEl.contentWindow != UNDEF) {
714
                return iframeEl.contentWindow.document;
715
            } else {
716
                throw module.createError("getIframeDocument: No Document object found for iframe element");
717
            }
718
        }
719
720
        function getIframeWindow(iframeEl) {
721
            if (typeof iframeEl.contentWindow != UNDEF) {
722
                return iframeEl.contentWindow;
723
            } else if (typeof iframeEl.contentDocument != UNDEF) {
724
                return iframeEl.contentDocument.defaultView;
725
            } else {
726
                throw module.createError("getIframeWindow: No Window object found for iframe element");
727
            }
728
        }
729
730
        // This looks bad. Is it worth it?
731
        function isWindow(obj) {
732
            return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
733
        }
734
735
        function getContentDocument(obj, module, methodName) {
736
            var doc;
737
738
            if (!obj) {
739
                doc = document;
740
            }
741
742
            // Test if a DOM node has been passed and obtain a document object for it if so
743
            else if (util.isHostProperty(obj, "nodeType")) {
744
                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...
745
                    getIframeDocument(obj) : getDocument(obj);
746
            }
747
748
            // Test if the doc parameter appears to be a Window object
749
            else if (isWindow(obj)) {
750
                doc = obj.document;
751
            }
752
753
            if (!doc) {
754
                throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
755
            }
756
757
            return doc;
758
        }
759
760
        function getRootContainer(node) {
761
            var parent;
762
            while ( (parent = node.parentNode) ) {
763
                node = parent;
764
            }
765
            return node;
766
        }
767
768
        function comparePoints(nodeA, offsetA, nodeB, offsetB) {
769
            // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
770
            var nodeC, root, childA, childB, n;
771
            if (nodeA == nodeB) {
772
                // Case 1: nodes are the same
773
                return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
774
            } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
775
                // Case 2: node C (container B or an ancestor) is a child node of A
776
                return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
777
            } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
778
                // Case 3: node C (container A or an ancestor) is a child node of B
779
                return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
780
            } else {
781
                root = getCommonAncestor(nodeA, nodeB);
782
                if (!root) {
783
                    throw new Error("comparePoints error: nodes have no common ancestor");
784
                }
785
786
                // Case 4: containers are siblings or descendants of siblings
787
                childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
788
                childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
789
790
                if (childA === childB) {
791
                    // This shouldn't be possible
792
                    throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
793
                } else {
794
                    n = root.firstChild;
795
                    while (n) {
796
                        if (n === childA) {
797
                            return -1;
798
                        } else if (n === childB) {
799
                            return 1;
800
                        }
801
                        n = n.nextSibling;
802
                    }
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...
803
                }
804
            }
805
        }
806
807
        /*----------------------------------------------------------------------------------------------------------------*/
808
809
        // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
810
        var crashyTextNodes = false;
811
812
        function isBrokenNode(node) {
813
            var n;
814
            try {
815
                n = node.parentNode;
0 ignored issues
show
Unused Code introduced by
The variable n seems to be never used. Consider removing it.
Loading history...
816
                return false;
817
            } catch (e) {
818
                return true;
819
            }
820
        }
821
822
        (function() {
823
            var el = document.createElement("b");
824
            el.innerHTML = "1";
825
            var textNode = el.firstChild;
826
            el.innerHTML = "<br>";
827
            crashyTextNodes = isBrokenNode(textNode);
828
829
            api.features.crashyTextNodes = crashyTextNodes;
830
        })();
831
832
        /*----------------------------------------------------------------------------------------------------------------*/
833
834
        function inspectNode(node) {
835
            if (!node) {
836
                return "[No node]";
837
            }
838
            if (crashyTextNodes && isBrokenNode(node)) {
839
                return "[Broken node]";
840
            }
841
            if (isCharacterDataNode(node)) {
842
                return '"' + node.data + '"';
843
            }
844
            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...
845
                var idAttr = node.id ? ' id="' + node.id + '"' : "";
846
                return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
847
            }
848
            return node.nodeName;
849
        }
850
851
        function fragmentFromNodeChildren(node) {
852
            var fragment = getDocument(node).createDocumentFragment(), child;
853
            while ( (child = node.firstChild) ) {
854
                fragment.appendChild(child);
855
            }
856
            return fragment;
857
        }
858
859
        var getComputedStyleProperty;
860
        if (typeof window.getComputedStyle != UNDEF) {
861
            getComputedStyleProperty = function(el, propName) {
862
                return getWindow(el).getComputedStyle(el, null)[propName];
863
            };
864
        } else if (typeof document.documentElement.currentStyle != UNDEF) {
865
            getComputedStyleProperty = function(el, propName) {
866
                return el.currentStyle[propName];
867
            };
868
        } else {
869
            module.fail("No means of obtaining computed style properties found");
870
        }
871
872
        function NodeIterator(root) {
873
            this.root = root;
874
            this._next = root;
875
        }
876
877
        NodeIterator.prototype = {
878
            _current: null,
879
880
            hasNext: function() {
881
                return !!this._next;
882
            },
883
884
            next: function() {
885
                var n = this._current = this._next;
886
                var child, next;
887
                if (this._current) {
888
                    child = n.firstChild;
889
                    if (child) {
890
                        this._next = child;
891
                    } else {
892
                        next = null;
893
                        while ((n !== this.root) && !(next = n.nextSibling)) {
894
                            n = n.parentNode;
895
                        }
896
                        this._next = next;
897
                    }
898
                }
899
                return this._current;
900
            },
901
902
            detach: function() {
903
                this._current = this._next = this.root = null;
904
            }
905
        };
906
907
        function createIterator(root) {
908
            return new NodeIterator(root);
909
        }
910
911
        function DomPosition(node, offset) {
912
            this.node = node;
913
            this.offset = offset;
914
        }
915
916
        DomPosition.prototype = {
917
            equals: function(pos) {
918
                return !!pos && this.node === pos.node && this.offset == pos.offset;
919
            },
920
921
            inspect: function() {
922
                return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
923
            },
924
925
            toString: function() {
926
                return this.inspect();
927
            }
928
        };
929
930
        function DOMException(codeName) {
931
            this.code = this[codeName];
932
            this.codeName = codeName;
933
            this.message = "DOMException: " + this.codeName;
934
        }
935
936
        DOMException.prototype = {
937
            INDEX_SIZE_ERR: 1,
938
            HIERARCHY_REQUEST_ERR: 3,
939
            WRONG_DOCUMENT_ERR: 4,
940
            NO_MODIFICATION_ALLOWED_ERR: 7,
941
            NOT_FOUND_ERR: 8,
942
            NOT_SUPPORTED_ERR: 9,
943
            INVALID_STATE_ERR: 11,
944
            INVALID_NODE_TYPE_ERR: 24
945
        };
946
947
        DOMException.prototype.toString = function() {
948
            return this.message;
949
        };
950
951
        api.dom = {
952
            arrayContains: arrayContains,
953
            isHtmlNamespace: isHtmlNamespace,
954
            parentElement: parentElement,
955
            getNodeIndex: getNodeIndex,
956
            getNodeLength: getNodeLength,
957
            getCommonAncestor: getCommonAncestor,
958
            isAncestorOf: isAncestorOf,
959
            isOrIsAncestorOf: isOrIsAncestorOf,
960
            getClosestAncestorIn: getClosestAncestorIn,
961
            isCharacterDataNode: isCharacterDataNode,
962
            isTextOrCommentNode: isTextOrCommentNode,
963
            insertAfter: insertAfter,
964
            splitDataNode: splitDataNode,
965
            getDocument: getDocument,
966
            getWindow: getWindow,
967
            getIframeWindow: getIframeWindow,
968
            getIframeDocument: getIframeDocument,
969
            getBody: util.getBody,
970
            isWindow: isWindow,
971
            getContentDocument: getContentDocument,
972
            getRootContainer: getRootContainer,
973
            comparePoints: comparePoints,
974
            isBrokenNode: isBrokenNode,
975
            inspectNode: inspectNode,
976
            getComputedStyleProperty: getComputedStyleProperty,
0 ignored issues
show
Bug introduced by
The variable getComputedStyleProperty seems to not be initialized for all possible execution paths.
Loading history...
977
            fragmentFromNodeChildren: fragmentFromNodeChildren,
978
            createIterator: createIterator,
979
            DomPosition: DomPosition
980
        };
981
982
        api.DOMException = DOMException;
983
    });
984
985
    /*----------------------------------------------------------------------------------------------------------------*/
986
987
    // Pure JavaScript implementation of DOM Range
988
    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...
989
        var dom = api.dom;
990
        var util = api.util;
991
        var DomPosition = dom.DomPosition;
992
        var DOMException = api.DOMException;
993
994
        var isCharacterDataNode = dom.isCharacterDataNode;
995
        var getNodeIndex = dom.getNodeIndex;
996
        var isOrIsAncestorOf = dom.isOrIsAncestorOf;
997
        var getDocument = dom.getDocument;
998
        var comparePoints = dom.comparePoints;
999
        var splitDataNode = dom.splitDataNode;
1000
        var getClosestAncestorIn = dom.getClosestAncestorIn;
1001
        var getNodeLength = dom.getNodeLength;
1002
        var arrayContains = dom.arrayContains;
1003
        var getRootContainer = dom.getRootContainer;
1004
        var crashyTextNodes = api.features.crashyTextNodes;
1005
1006
        /*----------------------------------------------------------------------------------------------------------------*/
1007
1008
        // Utility functions
1009
1010
        function isNonTextPartiallySelected(node, range) {
1011
            return (node.nodeType != 3) &&
1012
                   (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
1013
        }
1014
1015
        function getRangeDocument(range) {
1016
            return range.document || getDocument(range.startContainer);
1017
        }
1018
1019
        function getBoundaryBeforeNode(node) {
1020
            return new DomPosition(node.parentNode, getNodeIndex(node));
1021
        }
1022
1023
        function getBoundaryAfterNode(node) {
1024
            return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
1025
        }
1026
1027
        function insertNodeAtPosition(node, n, o) {
1028
            var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
1029
            if (isCharacterDataNode(n)) {
1030
                if (o == n.length) {
1031
                    dom.insertAfter(node, n);
1032
                } else {
1033
                    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...
1034
                }
1035
            } else if (o >= n.childNodes.length) {
1036
                n.appendChild(node);
1037
            } else {
1038
                n.insertBefore(node, n.childNodes[o]);
1039
            }
1040
            return firstNodeInserted;
1041
        }
1042
1043
        function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
1044
            assertRangeValid(rangeA);
1045
            assertRangeValid(rangeB);
1046
1047
            if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
1048
                throw new DOMException("WRONG_DOCUMENT_ERR");
1049
            }
1050
1051
            var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
1052
                endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
1053
1054
            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1055
        }
1056
1057
        function cloneSubtree(iterator) {
1058
            var partiallySelected;
1059
            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1060
                partiallySelected = iterator.isPartiallySelectedSubtree();
1061
                node = node.cloneNode(!partiallySelected);
1062
                if (partiallySelected) {
1063
                    subIterator = iterator.getSubtreeIterator();
1064
                    node.appendChild(cloneSubtree(subIterator));
1065
                    subIterator.detach();
1066
                }
1067
1068
                if (node.nodeType == 10) { // DocumentType
1069
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1070
                }
1071
                frag.appendChild(node);
1072
            }
1073
            return frag;
1074
        }
1075
1076
        function iterateSubtree(rangeIterator, func, iteratorState) {
1077
            var it, n;
1078
            iteratorState = iteratorState || { stop: false };
1079
            for (var node, subRangeIterator; node = rangeIterator.next(); ) {
1080
                if (rangeIterator.isPartiallySelectedSubtree()) {
1081
                    if (func(node) === false) {
1082
                        iteratorState.stop = true;
1083
                        return;
1084
                    } 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...
1085
                        // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
1086
                        // the node selected by the Range.
1087
                        subRangeIterator = rangeIterator.getSubtreeIterator();
1088
                        iterateSubtree(subRangeIterator, func, iteratorState);
1089
                        subRangeIterator.detach();
1090
                        if (iteratorState.stop) {
1091
                            return;
1092
                        }
1093
                    }
1094
                } else {
1095
                    // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
1096
                    // descendants
1097
                    it = dom.createIterator(node);
1098
                    while ( (n = it.next()) ) {
1099
                        if (func(n) === false) {
1100
                            iteratorState.stop = true;
1101
                            return;
1102
                        }
1103
                    }
1104
                }
1105
            }
1106
        }
1107
1108
        function deleteSubtree(iterator) {
1109
            var subIterator;
1110
            while (iterator.next()) {
1111
                if (iterator.isPartiallySelectedSubtree()) {
1112
                    subIterator = iterator.getSubtreeIterator();
1113
                    deleteSubtree(subIterator);
1114
                    subIterator.detach();
1115
                } else {
1116
                    iterator.remove();
1117
                }
1118
            }
1119
        }
1120
1121
        function extractSubtree(iterator) {
1122
            for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1123
1124
                if (iterator.isPartiallySelectedSubtree()) {
1125
                    node = node.cloneNode(false);
1126
                    subIterator = iterator.getSubtreeIterator();
1127
                    node.appendChild(extractSubtree(subIterator));
1128
                    subIterator.detach();
1129
                } else {
1130
                    iterator.remove();
1131
                }
1132
                if (node.nodeType == 10) { // DocumentType
1133
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1134
                }
1135
                frag.appendChild(node);
1136
            }
1137
            return frag;
1138
        }
1139
1140
        function getNodesInRange(range, nodeTypes, filter) {
1141
            var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
1142
            var filterExists = !!filter;
1143
            if (filterNodeTypes) {
1144
                regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
1145
            }
1146
1147
            var nodes = [];
1148
            iterateSubtree(new RangeIterator(range, false), function(node) {
1149
                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 1143 is false. Are you sure this can never be the case?
Loading history...
1150
                    return;
1151
                }
1152
                if (filterExists && !filter(node)) {
1153
                    return;
1154
                }
1155
                // Don't include a boundary container if it is a character data node and the range does not contain any
1156
                // of its character data. See issue 190.
1157
                var sc = range.startContainer;
1158
                if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
1159
                    return;
1160
                }
1161
1162
                var ec = range.endContainer;
1163
                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...
1164
                    return;
1165
                }
1166
1167
                nodes.push(node);
1168
            });
1169
            return nodes;
1170
        }
1171
1172
        function inspect(range) {
1173
            var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
1174
            return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
1175
                    dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
1176
        }
1177
1178
        /*----------------------------------------------------------------------------------------------------------------*/
1179
1180
        // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
1181
1182
        function RangeIterator(range, clonePartiallySelectedTextNodes) {
1183
            this.range = range;
1184
            this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
1185
1186
1187
            if (!range.collapsed) {
1188
                this.sc = range.startContainer;
1189
                this.so = range.startOffset;
1190
                this.ec = range.endContainer;
1191
                this.eo = range.endOffset;
1192
                var root = range.commonAncestorContainer;
1193
1194
                if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
1195
                    this.isSingleCharacterDataNode = true;
1196
                    this._first = this._last = this._next = this.sc;
1197
                } else {
1198
                    this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
1199
                        this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
1200
                    this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
1201
                        this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
1202
                }
1203
            }
1204
        }
1205
1206
        RangeIterator.prototype = {
1207
            _current: null,
1208
            _next: null,
1209
            _first: null,
1210
            _last: null,
1211
            isSingleCharacterDataNode: false,
1212
1213
            reset: function() {
1214
                this._current = null;
1215
                this._next = this._first;
1216
            },
1217
1218
            hasNext: function() {
1219
                return !!this._next;
1220
            },
1221
1222
            next: function() {
1223
                // Move to next node
1224
                var current = this._current = this._next;
1225
                if (current) {
1226
                    this._next = (current !== this._last) ? current.nextSibling : null;
1227
1228
                    // Check for partially selected text nodes
1229
                    if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
1230
                        if (current === this.ec) {
1231
                            (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
1232
                        }
1233
                        if (this._current === this.sc) {
1234
                            (current = current.cloneNode(true)).deleteData(0, this.so);
1235
                        }
1236
                    }
1237
                }
1238
1239
                return current;
1240
            },
1241
1242
            remove: function() {
1243
                var current = this._current, start, end;
1244
1245
                if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
1246
                    start = (current === this.sc) ? this.so : 0;
1247
                    end = (current === this.ec) ? this.eo : current.length;
1248
                    if (start != end) {
1249
                        current.deleteData(start, end - start);
1250
                    }
1251
                } else {
1252
                    if (current.parentNode) {
1253
                        current.parentNode.removeChild(current);
1254
                    } 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...
1255
                    }
1256
                }
1257
            },
1258
1259
            // Checks if the current node is partially selected
1260
            isPartiallySelectedSubtree: function() {
1261
                var current = this._current;
1262
                return isNonTextPartiallySelected(current, this.range);
1263
            },
1264
1265
            getSubtreeIterator: function() {
1266
                var subRange;
1267
                if (this.isSingleCharacterDataNode) {
1268
                    subRange = this.range.cloneRange();
1269
                    subRange.collapse(false);
1270
                } else {
1271
                    subRange = new Range(getRangeDocument(this.range));
1272
                    var current = this._current;
1273
                    var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
1274
1275
                    if (isOrIsAncestorOf(current, this.sc)) {
1276
                        startContainer = this.sc;
1277
                        startOffset = this.so;
1278
                    }
1279
                    if (isOrIsAncestorOf(current, this.ec)) {
1280
                        endContainer = this.ec;
1281
                        endOffset = this.eo;
1282
                    }
1283
1284
                    updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
1285
                }
1286
                return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
1287
            },
1288
1289
            detach: function() {
1290
                this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
1291
            }
1292
        };
1293
1294
        /*----------------------------------------------------------------------------------------------------------------*/
1295
1296
        var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
1297
        var rootContainerNodeTypes = [2, 9, 11];
1298
        var readonlyNodeTypes = [5, 6, 10, 12];
1299
        var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
1300
        var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
1301
1302
        function createAncestorFinder(nodeTypes) {
1303
            return function(node, selfIsAncestor) {
1304
                var t, n = selfIsAncestor ? node : node.parentNode;
1305
                while (n) {
1306
                    t = n.nodeType;
1307
                    if (arrayContains(nodeTypes, t)) {
1308
                        return n;
1309
                    }
1310
                    n = n.parentNode;
1311
                }
1312
                return null;
1313
            };
1314
        }
1315
1316
        var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
1317
        var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
1318
        var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
1319
1320
        function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
1321
            if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
1322
                throw new DOMException("INVALID_NODE_TYPE_ERR");
1323
            }
1324
        }
1325
1326
        function assertValidNodeType(node, invalidTypes) {
1327
            if (!arrayContains(invalidTypes, node.nodeType)) {
1328
                throw new DOMException("INVALID_NODE_TYPE_ERR");
1329
            }
1330
        }
1331
1332
        function assertValidOffset(node, offset) {
1333
            if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
1334
                throw new DOMException("INDEX_SIZE_ERR");
1335
            }
1336
        }
1337
1338
        function assertSameDocumentOrFragment(node1, node2) {
1339
            if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
1340
                throw new DOMException("WRONG_DOCUMENT_ERR");
1341
            }
1342
        }
1343
1344
        function assertNodeNotReadOnly(node) {
1345
            if (getReadonlyAncestor(node, true)) {
1346
                throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
1347
            }
1348
        }
1349
1350
        function assertNode(node, codeName) {
1351
            if (!node) {
1352
                throw new DOMException(codeName);
1353
            }
1354
        }
1355
1356
        function isOrphan(node) {
1357
            return (crashyTextNodes && dom.isBrokenNode(node)) ||
1358
                !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
1359
        }
1360
1361
        function isValidOffset(node, offset) {
1362
            return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
1363
        }
1364
1365
        function isRangeValid(range) {
1366
            return (!!range.startContainer && !!range.endContainer &&
1367
                    !isOrphan(range.startContainer) &&
1368
                    !isOrphan(range.endContainer) &&
1369
                    isValidOffset(range.startContainer, range.startOffset) &&
1370
                    isValidOffset(range.endContainer, range.endOffset));
1371
        }
1372
1373
        function assertRangeValid(range) {
1374
            if (!isRangeValid(range)) {
1375
                throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
1376
            }
1377
        }
1378
1379
        /*----------------------------------------------------------------------------------------------------------------*/
1380
1381
        // Test the browser's innerHTML support to decide how to implement createContextualFragment
1382
        var styleEl = document.createElement("style");
1383
        var htmlParsingConforms = false;
1384
        try {
1385
            styleEl.innerHTML = "<b>x</b>";
1386
            htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
1387
        } 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...
1388
            // IE 6 and 7 throw
1389
        }
1390
1391
        api.features.htmlParsingConforms = htmlParsingConforms;
1392
1393
        var createContextualFragment = htmlParsingConforms ?
1394
1395
            // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
1396
            // discussion and base code for this implementation at issue 67.
1397
            // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
1398
            // Thanks to Aleks Williams.
1399
            function(fragmentStr) {
1400
                // "Let node the context object's start's node."
1401
                var node = this.startContainer;
1402
                var doc = getDocument(node);
1403
1404
                // "If the context object's start's node is null, raise an INVALID_STATE_ERR
1405
                // exception and abort these steps."
1406
                if (!node) {
1407
                    throw new DOMException("INVALID_STATE_ERR");
1408
                }
1409
1410
                // "Let element be as follows, depending on node's interface:"
1411
                // Document, Document Fragment: null
1412
                var el = null;
1413
1414
                // "Element: node"
1415
                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...
1416
                    el = node;
1417
1418
                // "Text, Comment: node's parentElement"
1419
                } else if (isCharacterDataNode(node)) {
1420
                    el = dom.parentElement(node);
1421
                }
1422
1423
                // "If either element is null or element's ownerDocument is an HTML document
1424
                // and element's local name is "html" and element's namespace is the HTML
1425
                // namespace"
1426
                if (el === null || (
1427
                    el.nodeName == "HTML" &&
1428
                    dom.isHtmlNamespace(getDocument(el).documentElement) &&
1429
                    dom.isHtmlNamespace(el)
1430
                )) {
1431
1432
                // "let element be a new Element with "body" as its local name and the HTML
1433
                // namespace as its namespace.""
1434
                    el = doc.createElement("body");
1435
                } else {
1436
                    el = el.cloneNode(false);
1437
                }
1438
1439
                // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
1440
                // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
1441
                // "In either case, the algorithm must be invoked with fragment as the input
1442
                // and element as the context element."
1443
                el.innerHTML = fragmentStr;
1444
1445
                // "If this raises an exception, then abort these steps. Otherwise, let new
1446
                // children be the nodes returned."
1447
1448
                // "Let fragment be a new DocumentFragment."
1449
                // "Append all new children to fragment."
1450
                // "Return fragment."
1451
                return dom.fragmentFromNodeChildren(el);
1452
            } :
1453
1454
            // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
1455
            // previous versions of Rangy used (with the exception of using a body element rather than a div)
1456
            function(fragmentStr) {
1457
                var doc = getRangeDocument(this);
1458
                var el = doc.createElement("body");
1459
                el.innerHTML = fragmentStr;
1460
1461
                return dom.fragmentFromNodeChildren(el);
1462
            };
1463
1464
        function splitRangeBoundaries(range, positionsToPreserve) {
1465
            assertRangeValid(range);
1466
1467
            var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
1468
            var startEndSame = (sc === ec);
1469
1470
            if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
1471
                splitDataNode(ec, eo, positionsToPreserve);
1472
            }
1473
1474
            if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
1475
                sc = splitDataNode(sc, so, positionsToPreserve);
1476
                if (startEndSame) {
1477
                    eo -= so;
1478
                    ec = sc;
1479
                } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
1480
                    eo++;
1481
                }
1482
                so = 0;
1483
            }
1484
            range.setStartAndEnd(sc, so, ec, eo);
1485
        }
1486
        
1487
        function rangeToHtml(range) {
1488
            assertRangeValid(range);
1489
            var container = range.commonAncestorContainer.parentNode.cloneNode(false);
1490
            container.appendChild( range.cloneContents() );
1491
            return container.innerHTML;
1492
        }
1493
1494
        /*----------------------------------------------------------------------------------------------------------------*/
1495
1496
        var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
1497
            "commonAncestorContainer"];
1498
1499
        var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
1500
        var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
1501
1502
        util.extend(api.rangePrototype, {
1503
            compareBoundaryPoints: function(how, range) {
1504
                assertRangeValid(this);
1505
                assertSameDocumentOrFragment(this.startContainer, range.startContainer);
1506
1507
                var nodeA, offsetA, nodeB, offsetB;
1508
                var prefixA = (how == e2s || how == s2s) ? "start" : "end";
1509
                var prefixB = (how == s2e || how == s2s) ? "start" : "end";
1510
                nodeA = this[prefixA + "Container"];
1511
                offsetA = this[prefixA + "Offset"];
1512
                nodeB = range[prefixB + "Container"];
1513
                offsetB = range[prefixB + "Offset"];
1514
                return comparePoints(nodeA, offsetA, nodeB, offsetB);
1515
            },
1516
1517
            insertNode: function(node) {
1518
                assertRangeValid(this);
1519
                assertValidNodeType(node, insertableNodeTypes);
1520
                assertNodeNotReadOnly(this.startContainer);
1521
1522
                if (isOrIsAncestorOf(node, this.startContainer)) {
1523
                    throw new DOMException("HIERARCHY_REQUEST_ERR");
1524
                }
1525
1526
                // No check for whether the container of the start of the Range is of a type that does not allow
1527
                // children of the type of node: the browser's DOM implementation should do this for us when we attempt
1528
                // to add the node
1529
1530
                var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
1531
                this.setStartBefore(firstNodeInserted);
1532
            },
1533
1534
            cloneContents: function() {
1535
                assertRangeValid(this);
1536
1537
                var clone, frag;
1538
                if (this.collapsed) {
1539
                    return getRangeDocument(this).createDocumentFragment();
1540
                } 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...
1541
                    if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
1542
                        clone = this.startContainer.cloneNode(true);
1543
                        clone.data = clone.data.slice(this.startOffset, this.endOffset);
1544
                        frag = getRangeDocument(this).createDocumentFragment();
1545
                        frag.appendChild(clone);
1546
                        return frag;
1547
                    } 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...
1548
                        var iterator = new RangeIterator(this, true);
1549
                        clone = cloneSubtree(iterator);
1550
                        iterator.detach();
1551
                    }
1552
                    return clone;
1553
                }
1554
            },
1555
1556
            canSurroundContents: function() {
1557
                assertRangeValid(this);
1558
                assertNodeNotReadOnly(this.startContainer);
1559
                assertNodeNotReadOnly(this.endContainer);
1560
1561
                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1562
                // no non-text nodes.
1563
                var iterator = new RangeIterator(this, true);
1564
                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1565
                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1566
                iterator.detach();
1567
                return !boundariesInvalid;
1568
            },
1569
1570
            surroundContents: function(node) {
1571
                assertValidNodeType(node, surroundNodeTypes);
1572
1573
                if (!this.canSurroundContents()) {
1574
                    throw new DOMException("INVALID_STATE_ERR");
1575
                }
1576
1577
                // Extract the contents
1578
                var content = this.extractContents();
1579
1580
                // Clear the children of the node
1581
                if (node.hasChildNodes()) {
1582
                    while (node.lastChild) {
1583
                        node.removeChild(node.lastChild);
1584
                    }
1585
                }
1586
1587
                // Insert the new node and add the extracted contents
1588
                insertNodeAtPosition(node, this.startContainer, this.startOffset);
1589
                node.appendChild(content);
1590
1591
                this.selectNode(node);
1592
            },
1593
1594
            cloneRange: function() {
1595
                assertRangeValid(this);
1596
                var range = new Range(getRangeDocument(this));
1597
                var i = rangeProperties.length, prop;
1598
                while (i--) {
1599
                    prop = rangeProperties[i];
1600
                    range[prop] = this[prop];
1601
                }
1602
                return range;
1603
            },
1604
1605
            toString: function() {
1606
                assertRangeValid(this);
1607
                var sc = this.startContainer;
1608
                if (sc === this.endContainer && isCharacterDataNode(sc)) {
1609
                    return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
1610
                } 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...
1611
                    var textParts = [], iterator = new RangeIterator(this, true);
1612
                    iterateSubtree(iterator, function(node) {
1613
                        // Accept only text or CDATA nodes, not comments
1614
                        if (node.nodeType == 3 || node.nodeType == 4) {
1615
                            textParts.push(node.data);
1616
                        }
1617
                    });
1618
                    iterator.detach();
1619
                    return textParts.join("");
1620
                }
1621
            },
1622
1623
            // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
1624
            // been removed from Mozilla.
1625
1626
            compareNode: function(node) {
1627
                assertRangeValid(this);
1628
1629
                var parent = node.parentNode;
1630
                var nodeIndex = getNodeIndex(node);
1631
1632
                if (!parent) {
1633
                    throw new DOMException("NOT_FOUND_ERR");
1634
                }
1635
1636
                var startComparison = this.comparePoint(parent, nodeIndex),
1637
                    endComparison = this.comparePoint(parent, nodeIndex + 1);
1638
1639
                if (startComparison < 0) { // Node starts before
1640
                    return (endComparison > 0) ? n_b_a : n_b;
1641
                } 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...
1642
                    return (endComparison > 0) ? n_a : n_i;
1643
                }
1644
            },
1645
1646
            comparePoint: function(node, offset) {
1647
                assertRangeValid(this);
1648
                assertNode(node, "HIERARCHY_REQUEST_ERR");
1649
                assertSameDocumentOrFragment(node, this.startContainer);
1650
1651
                if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
1652
                    return -1;
1653
                } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
1654
                    return 1;
1655
                }
1656
                return 0;
1657
            },
1658
1659
            createContextualFragment: createContextualFragment,
1660
1661
            toHtml: function() {
1662
                return rangeToHtml(this);
1663
            },
1664
1665
            // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
1666
            // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
1667
            intersectsNode: function(node, touchingIsIntersecting) {
1668
                assertRangeValid(this);
1669
                assertNode(node, "NOT_FOUND_ERR");
1670
                if (getDocument(node) !== getRangeDocument(this)) {
1671
                    return false;
1672
                }
1673
1674
                var parent = node.parentNode, offset = getNodeIndex(node);
1675
                assertNode(parent, "NOT_FOUND_ERR");
1676
1677
                var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
1678
                    endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
1679
1680
                return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1681
            },
1682
1683
            isPointInRange: function(node, offset) {
1684
                assertRangeValid(this);
1685
                assertNode(node, "HIERARCHY_REQUEST_ERR");
1686
                assertSameDocumentOrFragment(node, this.startContainer);
1687
1688
                return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
1689
                       (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
1690
            },
1691
1692
            // The methods below are non-standard and invented by me.
1693
1694
            // Sharing a boundary start-to-end or end-to-start does not count as intersection.
1695
            intersectsRange: function(range) {
1696
                return rangesIntersect(this, range, false);
1697
            },
1698
1699
            // Sharing a boundary start-to-end or end-to-start does count as intersection.
1700
            intersectsOrTouchesRange: function(range) {
1701
                return rangesIntersect(this, range, true);
1702
            },
1703
1704
            intersection: function(range) {
1705
                if (this.intersectsRange(range)) {
1706
                    var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
1707
                        endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
1708
1709
                    var intersectionRange = this.cloneRange();
1710
                    if (startComparison == -1) {
1711
                        intersectionRange.setStart(range.startContainer, range.startOffset);
1712
                    }
1713
                    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...
1714
                        intersectionRange.setEnd(range.endContainer, range.endOffset);
1715
                    }
1716
                    return intersectionRange;
1717
                }
1718
                return null;
1719
            },
1720
1721
            union: function(range) {
1722
                if (this.intersectsOrTouchesRange(range)) {
1723
                    var unionRange = this.cloneRange();
1724
                    if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
1725
                        unionRange.setStart(range.startContainer, range.startOffset);
1726
                    }
1727
                    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...
1728
                        unionRange.setEnd(range.endContainer, range.endOffset);
1729
                    }
1730
                    return unionRange;
1731
                } 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...
1732
                    throw new DOMException("Ranges do not intersect");
1733
                }
1734
            },
1735
1736
            containsNode: function(node, allowPartial) {
1737
                if (allowPartial) {
1738
                    return this.intersectsNode(node, false);
1739
                } 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...
1740
                    return this.compareNode(node) == n_i;
1741
                }
1742
            },
1743
1744
            containsNodeContents: function(node) {
1745
                return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
1746
            },
1747
1748
            containsRange: function(range) {
1749
                var intersection = this.intersection(range);
1750
                return intersection !== null && range.equals(intersection);
1751
            },
1752
1753
            containsNodeText: function(node) {
1754
                var nodeRange = this.cloneRange();
1755
                nodeRange.selectNode(node);
1756
                var textNodes = nodeRange.getNodes([3]);
1757
                if (textNodes.length > 0) {
1758
                    nodeRange.setStart(textNodes[0], 0);
1759
                    var lastTextNode = textNodes.pop();
1760
                    nodeRange.setEnd(lastTextNode, lastTextNode.length);
1761
                    return this.containsRange(nodeRange);
1762
                } 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...
1763
                    return this.containsNodeContents(node);
1764
                }
1765
            },
1766
1767
            getNodes: function(nodeTypes, filter) {
1768
                assertRangeValid(this);
1769
                return getNodesInRange(this, nodeTypes, filter);
1770
            },
1771
1772
            getDocument: function() {
1773
                return getRangeDocument(this);
1774
            },
1775
1776
            collapseBefore: function(node) {
1777
                this.setEndBefore(node);
1778
                this.collapse(false);
1779
            },
1780
1781
            collapseAfter: function(node) {
1782
                this.setStartAfter(node);
1783
                this.collapse(true);
1784
            },
1785
            
1786
            getBookmark: function(containerNode) {
1787
                var doc = getRangeDocument(this);
1788
                var preSelectionRange = api.createRange(doc);
1789
                containerNode = containerNode || dom.getBody(doc);
1790
                preSelectionRange.selectNodeContents(containerNode);
1791
                var range = this.intersection(preSelectionRange);
1792
                var start = 0, end = 0;
1793
                if (range) {
1794
                    preSelectionRange.setEnd(range.startContainer, range.startOffset);
1795
                    start = preSelectionRange.toString().length;
1796
                    end = start + range.toString().length;
1797
                }
1798
1799
                return {
1800
                    start: start,
1801
                    end: end,
1802
                    containerNode: containerNode
1803
                };
1804
            },
1805
            
1806
            moveToBookmark: function(bookmark) {
1807
                var containerNode = bookmark.containerNode;
1808
                var charIndex = 0;
1809
                this.setStart(containerNode, 0);
1810
                this.collapse(true);
1811
                var nodeStack = [containerNode], node, foundStart = false, stop = false;
1812
                var nextCharIndex, i, childNodes;
1813
1814
                while (!stop && (node = nodeStack.pop())) {
1815
                    if (node.nodeType == 3) {
1816
                        nextCharIndex = charIndex + node.length;
1817
                        if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
1818
                            this.setStart(node, bookmark.start - charIndex);
1819
                            foundStart = true;
1820
                        }
1821
                        if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
1822
                            this.setEnd(node, bookmark.end - charIndex);
1823
                            stop = true;
1824
                        }
1825
                        charIndex = nextCharIndex;
1826
                    } else {
1827
                        childNodes = node.childNodes;
1828
                        i = childNodes.length;
1829
                        while (i--) {
1830
                            nodeStack.push(childNodes[i]);
1831
                        }
1832
                    }
1833
                }
1834
            },
1835
1836
            getName: function() {
1837
                return "DomRange";
1838
            },
1839
1840
            equals: function(range) {
1841
                return Range.rangesEqual(this, range);
1842
            },
1843
1844
            isValid: function() {
1845
                return isRangeValid(this);
1846
            },
1847
            
1848
            inspect: function() {
1849
                return inspect(this);
1850
            },
1851
            
1852
            detach: function() {
1853
                // In DOM4, detach() is now a no-op.
1854
            }
1855
        });
1856
1857
        function copyComparisonConstantsToObject(obj) {
1858
            obj.START_TO_START = s2s;
1859
            obj.START_TO_END = s2e;
1860
            obj.END_TO_END = e2e;
1861
            obj.END_TO_START = e2s;
1862
1863
            obj.NODE_BEFORE = n_b;
1864
            obj.NODE_AFTER = n_a;
1865
            obj.NODE_BEFORE_AND_AFTER = n_b_a;
1866
            obj.NODE_INSIDE = n_i;
1867
        }
1868
1869
        function copyComparisonConstants(constructor) {
1870
            copyComparisonConstantsToObject(constructor);
1871
            copyComparisonConstantsToObject(constructor.prototype);
1872
        }
1873
1874
        function createRangeContentRemover(remover, boundaryUpdater) {
1875
            return function() {
1876
                assertRangeValid(this);
1877
1878
                var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
1879
1880
                var iterator = new RangeIterator(this, true);
1881
1882
                // Work out where to position the range after content removal
1883
                var node, boundary;
1884
                if (sc !== root) {
1885
                    node = getClosestAncestorIn(sc, root, true);
1886
                    boundary = getBoundaryAfterNode(node);
1887
                    sc = boundary.node;
1888
                    so = boundary.offset;
1889
                }
1890
1891
                // Check none of the range is read-only
1892
                iterateSubtree(iterator, assertNodeNotReadOnly);
1893
1894
                iterator.reset();
1895
1896
                // Remove the content
1897
                var returnValue = remover(iterator);
1898
                iterator.detach();
1899
1900
                // Move to the new position
1901
                boundaryUpdater(this, sc, so, sc, so);
1902
1903
                return returnValue;
1904
            };
1905
        }
1906
1907
        function createPrototypeRange(constructor, boundaryUpdater) {
1908
            function createBeforeAfterNodeSetter(isBefore, isStart) {
1909
                return function(node) {
1910
                    assertValidNodeType(node, beforeAfterNodeTypes);
1911
                    assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
1912
1913
                    var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
1914
                    (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
1915
                };
1916
            }
1917
1918
            function setRangeStart(range, node, offset) {
1919
                var ec = range.endContainer, eo = range.endOffset;
1920
                if (node !== range.startContainer || offset !== range.startOffset) {
1921
                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
1922
                    // is after the current end. In either case, collapse the range to the new position
1923
                    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...
1924
                        ec = node;
1925
                        eo = offset;
1926
                    }
1927
                    boundaryUpdater(range, node, offset, ec, eo);
1928
                }
1929
            }
1930
1931
            function setRangeEnd(range, node, offset) {
1932
                var sc = range.startContainer, so = range.startOffset;
1933
                if (node !== range.endContainer || offset !== range.endOffset) {
1934
                    // Check the root containers of the range and the new boundary, and also check whether the new boundary
1935
                    // is after the current end. In either case, collapse the range to the new position
1936
                    if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
1937
                        sc = node;
1938
                        so = offset;
1939
                    }
1940
                    boundaryUpdater(range, sc, so, node, offset);
1941
                }
1942
            }
1943
1944
            // Set up inheritance
1945
            var F = function() {};
1946
            F.prototype = api.rangePrototype;
1947
            constructor.prototype = new F();
1948
1949
            util.extend(constructor.prototype, {
1950
                setStart: function(node, offset) {
1951
                    assertNoDocTypeNotationEntityAncestor(node, true);
1952
                    assertValidOffset(node, offset);
1953
1954
                    setRangeStart(this, node, offset);
1955
                },
1956
1957
                setEnd: function(node, offset) {
1958
                    assertNoDocTypeNotationEntityAncestor(node, true);
1959
                    assertValidOffset(node, offset);
1960
1961
                    setRangeEnd(this, node, offset);
1962
                },
1963
1964
                /**
1965
                 * Convenience method to set a range's start and end boundaries. Overloaded as follows:
1966
                 * - Two parameters (node, offset) creates a collapsed range at that position
1967
                 * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
1968
                 *   startOffset and ending at endOffset
1969
                 * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
1970
                 *   startNode and ending at endOffset in endNode
1971
                 */
1972
                setStartAndEnd: function() {
1973
                    var args = arguments;
1974
                    var sc = args[0], so = args[1], ec = sc, eo = so;
1975
1976
                    switch (args.length) {
1977
                        case 3:
1978
                            eo = args[2];
1979
                            break;
1980
                        case 4:
1981
                            ec = args[2];
1982
                            eo = args[3];
1983
                            break;
1984
                    }
1985
1986
                    boundaryUpdater(this, sc, so, ec, eo);
1987
                },
1988
                
1989
                setBoundary: function(node, offset, isStart) {
1990
                    this["set" + (isStart ? "Start" : "End")](node, offset);
1991
                },
1992
1993
                setStartBefore: createBeforeAfterNodeSetter(true, true),
1994
                setStartAfter: createBeforeAfterNodeSetter(false, true),
1995
                setEndBefore: createBeforeAfterNodeSetter(true, false),
1996
                setEndAfter: createBeforeAfterNodeSetter(false, false),
1997
1998
                collapse: function(isStart) {
1999
                    assertRangeValid(this);
2000
                    if (isStart) {
2001
                        boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
2002
                    } else {
2003
                        boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
2004
                    }
2005
                },
2006
2007
                selectNodeContents: function(node) {
2008
                    assertNoDocTypeNotationEntityAncestor(node, true);
2009
2010
                    boundaryUpdater(this, node, 0, node, getNodeLength(node));
2011
                },
2012
2013
                selectNode: function(node) {
2014
                    assertNoDocTypeNotationEntityAncestor(node, false);
2015
                    assertValidNodeType(node, beforeAfterNodeTypes);
2016
2017
                    var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
2018
                    boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
2019
                },
2020
2021
                extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
2022
2023
                deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
2024
2025
                canSurroundContents: function() {
2026
                    assertRangeValid(this);
2027
                    assertNodeNotReadOnly(this.startContainer);
2028
                    assertNodeNotReadOnly(this.endContainer);
2029
2030
                    // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
2031
                    // no non-text nodes.
2032
                    var iterator = new RangeIterator(this, true);
2033
                    var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
2034
                            (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
2035
                    iterator.detach();
2036
                    return !boundariesInvalid;
2037
                },
2038
2039
                splitBoundaries: function() {
2040
                    splitRangeBoundaries(this);
2041
                },
2042
2043
                splitBoundariesPreservingPositions: function(positionsToPreserve) {
2044
                    splitRangeBoundaries(this, positionsToPreserve);
2045
                },
2046
2047
                normalizeBoundaries: function() {
2048
                    assertRangeValid(this);
2049
2050
                    var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
2051
2052
                    var mergeForward = function(node) {
2053
                        var sibling = node.nextSibling;
2054
                        if (sibling && sibling.nodeType == node.nodeType) {
2055
                            ec = node;
2056
                            eo = node.length;
2057
                            node.appendData(sibling.data);
2058
                            sibling.parentNode.removeChild(sibling);
2059
                        }
2060
                    };
2061
2062
                    var mergeBackward = function(node) {
2063
                        var sibling = node.previousSibling;
2064
                        if (sibling && sibling.nodeType == node.nodeType) {
2065
                            sc = node;
2066
                            var nodeLength = node.length;
2067
                            so = sibling.length;
2068
                            node.insertData(0, sibling.data);
2069
                            sibling.parentNode.removeChild(sibling);
2070
                            if (sc == ec) {
2071
                                eo += so;
2072
                                ec = sc;
2073
                            } else if (ec == node.parentNode) {
2074
                                var nodeIndex = getNodeIndex(node);
2075
                                if (eo == nodeIndex) {
2076
                                    ec = node;
2077
                                    eo = nodeLength;
2078
                                } else if (eo > nodeIndex) {
2079
                                    eo--;
2080
                                }
2081
                            }
2082
                        }
2083
                    };
2084
2085
                    var normalizeStart = true;
2086
2087
                    if (isCharacterDataNode(ec)) {
2088
                        if (ec.length == eo) {
2089
                            mergeForward(ec);
2090
                        }
2091
                    } else {
2092
                        if (eo > 0) {
2093
                            var endNode = ec.childNodes[eo - 1];
2094
                            if (endNode && isCharacterDataNode(endNode)) {
2095
                                mergeForward(endNode);
2096
                            }
2097
                        }
2098
                        normalizeStart = !this.collapsed;
2099
                    }
2100
2101
                    if (normalizeStart) {
2102
                        if (isCharacterDataNode(sc)) {
2103
                            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...
2104
                                mergeBackward(sc);
2105
                            }
2106
                        } else {
2107
                            if (so < sc.childNodes.length) {
2108
                                var startNode = sc.childNodes[so];
2109
                                if (startNode && isCharacterDataNode(startNode)) {
2110
                                    mergeBackward(startNode);
2111
                                }
2112
                            }
2113
                        }
2114
                    } else {
2115
                        sc = ec;
2116
                        so = eo;
2117
                    }
2118
2119
                    boundaryUpdater(this, sc, so, ec, eo);
2120
                },
2121
2122
                collapseToPoint: function(node, offset) {
2123
                    assertNoDocTypeNotationEntityAncestor(node, true);
2124
                    assertValidOffset(node, offset);
2125
                    this.setStartAndEnd(node, offset);
2126
                }
2127
            });
2128
2129
            copyComparisonConstants(constructor);
2130
        }
2131
2132
        /*----------------------------------------------------------------------------------------------------------------*/
2133
2134
        // Updates commonAncestorContainer and collapsed after boundary change
2135
        function updateCollapsedAndCommonAncestor(range) {
2136
            range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2137
            range.commonAncestorContainer = range.collapsed ?
2138
                range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
2139
        }
2140
2141
        function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
2142
            range.startContainer = startContainer;
2143
            range.startOffset = startOffset;
2144
            range.endContainer = endContainer;
2145
            range.endOffset = endOffset;
2146
            range.document = dom.getDocument(startContainer);
2147
2148
            updateCollapsedAndCommonAncestor(range);
2149
        }
2150
2151
        function Range(doc) {
2152
            this.startContainer = doc;
2153
            this.startOffset = 0;
2154
            this.endContainer = doc;
2155
            this.endOffset = 0;
2156
            this.document = doc;
2157
            updateCollapsedAndCommonAncestor(this);
2158
        }
2159
2160
        createPrototypeRange(Range, updateBoundaries);
2161
2162
        util.extend(Range, {
2163
            rangeProperties: rangeProperties,
2164
            RangeIterator: RangeIterator,
2165
            copyComparisonConstants: copyComparisonConstants,
2166
            createPrototypeRange: createPrototypeRange,
2167
            inspect: inspect,
2168
            toHtml: rangeToHtml,
2169
            getRangeDocument: getRangeDocument,
2170
            rangesEqual: function(r1, r2) {
2171
                return r1.startContainer === r2.startContainer &&
2172
                    r1.startOffset === r2.startOffset &&
2173
                    r1.endContainer === r2.endContainer &&
2174
                    r1.endOffset === r2.endOffset;
2175
            }
2176
        });
2177
2178
        api.DomRange = Range;
2179
    });
2180
2181
    /*----------------------------------------------------------------------------------------------------------------*/
2182
2183
    // Wrappers for the browser's native DOM Range and/or TextRange implementation 
2184
    api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
2185
        var WrappedRange, WrappedTextRange;
2186
        var dom = api.dom;
2187
        var util = api.util;
2188
        var DomPosition = dom.DomPosition;
2189
        var DomRange = api.DomRange;
2190
        var getBody = dom.getBody;
2191
        var getContentDocument = dom.getContentDocument;
2192
        var isCharacterDataNode = dom.isCharacterDataNode;
2193
2194
2195
        /*----------------------------------------------------------------------------------------------------------------*/
2196
2197
        if (api.features.implementsDomRange) {
2198
            // This is a wrapper around the browser's native DOM Range. It has two aims:
2199
            // - Provide workarounds for specific browser bugs
2200
            // - provide convenient extensions, which are inherited from Rangy's DomRange
2201
2202
            (function() {
2203
                var rangeProto;
2204
                var rangeProperties = DomRange.rangeProperties;
2205
2206
                function updateRangeProperties(range) {
2207
                    var i = rangeProperties.length, prop;
2208
                    while (i--) {
2209
                        prop = rangeProperties[i];
2210
                        range[prop] = range.nativeRange[prop];
2211
                    }
2212
                    // Fix for broken collapsed property in IE 9.
2213
                    range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2214
                }
2215
2216
                function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
2217
                    var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2218
                    var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2219
                    var nativeRangeDifferent = !range.equals(range.nativeRange);
2220
2221
                    // Always set both boundaries for the benefit of IE9 (see issue 35)
2222
                    if (startMoved || endMoved || nativeRangeDifferent) {
2223
                        range.setEnd(endContainer, endOffset);
2224
                        range.setStart(startContainer, startOffset);
2225
                    }
2226
                }
2227
2228
                var createBeforeAfterNodeSetter;
2229
2230
                WrappedRange = function(range) {
2231
                    if (!range) {
2232
                        throw module.createError("WrappedRange: Range must be specified");
2233
                    }
2234
                    this.nativeRange = range;
2235
                    updateRangeProperties(this);
2236
                };
2237
2238
                DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
2239
2240
                rangeProto = WrappedRange.prototype;
2241
2242
                rangeProto.selectNode = function(node) {
2243
                    this.nativeRange.selectNode(node);
2244
                    updateRangeProperties(this);
2245
                };
2246
2247
                rangeProto.cloneContents = function() {
2248
                    return this.nativeRange.cloneContents();
2249
                };
2250
2251
                // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
2252
                // insertNode() is never delegated to the native range.
2253
2254
                rangeProto.surroundContents = function(node) {
2255
                    this.nativeRange.surroundContents(node);
2256
                    updateRangeProperties(this);
2257
                };
2258
2259
                rangeProto.collapse = function(isStart) {
2260
                    this.nativeRange.collapse(isStart);
2261
                    updateRangeProperties(this);
2262
                };
2263
2264
                rangeProto.cloneRange = function() {
2265
                    return new WrappedRange(this.nativeRange.cloneRange());
2266
                };
2267
2268
                rangeProto.refresh = function() {
2269
                    updateRangeProperties(this);
2270
                };
2271
2272
                rangeProto.toString = function() {
2273
                    return this.nativeRange.toString();
2274
                };
2275
2276
                // Create test range and node for feature detection
2277
2278
                var testTextNode = document.createTextNode("test");
2279
                getBody(document).appendChild(testTextNode);
2280
                var range = document.createRange();
2281
2282
                /*--------------------------------------------------------------------------------------------------------*/
2283
2284
                // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2285
                // correct for it
2286
2287
                range.setStart(testTextNode, 0);
2288
                range.setEnd(testTextNode, 0);
2289
2290
                try {
2291
                    range.setStart(testTextNode, 1);
2292
2293
                    rangeProto.setStart = function(node, offset) {
2294
                        this.nativeRange.setStart(node, offset);
2295
                        updateRangeProperties(this);
2296
                    };
2297
2298
                    rangeProto.setEnd = function(node, offset) {
2299
                        this.nativeRange.setEnd(node, offset);
2300
                        updateRangeProperties(this);
2301
                    };
2302
2303
                    createBeforeAfterNodeSetter = function(name) {
2304
                        return function(node) {
2305
                            this.nativeRange[name](node);
2306
                            updateRangeProperties(this);
2307
                        };
2308
                    };
2309
2310
                } catch(ex) {
2311
2312
                    rangeProto.setStart = function(node, offset) {
2313
                        try {
2314
                            this.nativeRange.setStart(node, offset);
2315
                        } catch (ex) {
2316
                            this.nativeRange.setEnd(node, offset);
2317
                            this.nativeRange.setStart(node, offset);
2318
                        }
2319
                        updateRangeProperties(this);
2320
                    };
2321
2322
                    rangeProto.setEnd = function(node, offset) {
2323
                        try {
2324
                            this.nativeRange.setEnd(node, offset);
2325
                        } catch (ex) {
2326
                            this.nativeRange.setStart(node, offset);
2327
                            this.nativeRange.setEnd(node, offset);
2328
                        }
2329
                        updateRangeProperties(this);
2330
                    };
2331
2332
                    createBeforeAfterNodeSetter = function(name, oppositeName) {
2333
                        return function(node) {
2334
                            try {
2335
                                this.nativeRange[name](node);
2336
                            } catch (ex) {
2337
                                this.nativeRange[oppositeName](node);
2338
                                this.nativeRange[name](node);
2339
                            }
2340
                            updateRangeProperties(this);
2341
                        };
2342
                    };
2343
                }
2344
2345
                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...
2346
                rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2347
                rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2348
                rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2349
2350
                /*--------------------------------------------------------------------------------------------------------*/
2351
2352
                // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
2353
                // whether the native implementation can be trusted
2354
                rangeProto.selectNodeContents = function(node) {
2355
                    this.setStartAndEnd(node, 0, dom.getNodeLength(node));
2356
                };
2357
2358
                /*--------------------------------------------------------------------------------------------------------*/
2359
2360
                // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
2361
                // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2362
2363
                range.selectNodeContents(testTextNode);
2364
                range.setEnd(testTextNode, 3);
2365
2366
                var range2 = document.createRange();
2367
                range2.selectNodeContents(testTextNode);
2368
                range2.setEnd(testTextNode, 4);
2369
                range2.setStart(testTextNode, 2);
2370
2371
                if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
2372
                        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...
2373
                    // This is the wrong way round, so correct for it
2374
2375
                    rangeProto.compareBoundaryPoints = function(type, range) {
2376
                        range = range.nativeRange || range;
2377
                        if (type == range.START_TO_END) {
2378
                            type = range.END_TO_START;
2379
                        } else if (type == range.END_TO_START) {
2380
                            type = range.START_TO_END;
2381
                        }
2382
                        return this.nativeRange.compareBoundaryPoints(type, range);
2383
                    };
2384
                } else {
2385
                    rangeProto.compareBoundaryPoints = function(type, range) {
2386
                        return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2387
                    };
2388
                }
2389
2390
                /*--------------------------------------------------------------------------------------------------------*/
2391
2392
                // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107.
2393
2394
                var el = document.createElement("div");
2395
                el.innerHTML = "123";
2396
                var textNode = el.firstChild;
2397
                var body = getBody(document);
2398
                body.appendChild(el);
2399
2400
                range.setStart(textNode, 1);
2401
                range.setEnd(textNode, 2);
2402
                range.deleteContents();
2403
2404
                if (textNode.data == "13") {
2405
                    // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
2406
                    // extractContents()
2407
                    rangeProto.deleteContents = function() {
2408
                        this.nativeRange.deleteContents();
2409
                        updateRangeProperties(this);
2410
                    };
2411
2412
                    rangeProto.extractContents = function() {
2413
                        var frag = this.nativeRange.extractContents();
2414
                        updateRangeProperties(this);
2415
                        return frag;
2416
                    };
2417
                } 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...
2418
                }
2419
2420
                body.removeChild(el);
2421
                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...
2422
2423
                /*--------------------------------------------------------------------------------------------------------*/
2424
2425
                // Test for existence of createContextualFragment and delegate to it if it exists
2426
                if (util.isHostMethod(range, "createContextualFragment")) {
2427
                    rangeProto.createContextualFragment = function(fragmentStr) {
2428
                        return this.nativeRange.createContextualFragment(fragmentStr);
2429
                    };
2430
                }
2431
2432
                /*--------------------------------------------------------------------------------------------------------*/
2433
2434
                // Clean up
2435
                getBody(document).removeChild(testTextNode);
2436
2437
                rangeProto.getName = function() {
2438
                    return "WrappedRange";
2439
                };
2440
2441
                api.WrappedRange = WrappedRange;
2442
2443
                api.createNativeRange = function(doc) {
2444
                    doc = getContentDocument(doc, module, "createNativeRange");
2445
                    return doc.createRange();
2446
                };
2447
            })();
2448
        }
2449
        
2450
        if (api.features.implementsTextRange) {
2451
            /*
2452
            This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
2453
            method. For example, in the following (where pipes denote the selection boundaries):
2454
2455
            <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
2456
2457
            var range = document.selection.createRange();
2458
            alert(range.parentElement().id); // Should alert "ul" but alerts "b"
2459
2460
            This method returns the common ancestor node of the following:
2461
            - the parentElement() of the textRange
2462
            - the parentElement() of the textRange after calling collapse(true)
2463
            - the parentElement() of the textRange after calling collapse(false)
2464
            */
2465
            var getTextRangeContainerElement = function(textRange) {
2466
                var parentEl = textRange.parentElement();
2467
                var range = textRange.duplicate();
2468
                range.collapse(true);
2469
                var startEl = range.parentElement();
2470
                range = textRange.duplicate();
2471
                range.collapse(false);
2472
                var endEl = range.parentElement();
2473
                var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
2474
2475
                return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
2476
            };
2477
2478
            var textRangeIsCollapsed = function(textRange) {
2479
                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...
2480
            };
2481
2482
            // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
2483
            // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
2484
            // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
2485
            // bugs, handling for inputs and images, plus optimizations.
2486
            var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
2487
                var workingRange = textRange.duplicate();
2488
                workingRange.collapse(isStart);
2489
                var containerElement = workingRange.parentElement();
2490
2491
                // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
2492
                // check for that
2493
                if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
2494
                    containerElement = wholeRangeContainerElement;
2495
                }
2496
2497
2498
                // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
2499
                // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
2500
                if (!containerElement.canHaveHTML) {
2501
                    var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
2502
                    return {
2503
                        boundaryPosition: pos,
2504
                        nodeInfo: {
2505
                            nodeIndex: pos.offset,
2506
                            containerElement: pos.node
2507
                        }
2508
                    };
2509
                }
2510
2511
                var workingNode = dom.getDocument(containerElement).createElement("span");
2512
2513
                // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
2514
                // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
2515
                if (workingNode.parentNode) {
2516
                    workingNode.parentNode.removeChild(workingNode);
2517
                }
2518
2519
                var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
2520
                var previousNode, nextNode, boundaryPosition, boundaryNode;
2521
                var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
2522
                var childNodeCount = containerElement.childNodes.length;
2523
                var end = childNodeCount;
2524
2525
                // Check end first. Code within the loop assumes that the endth child node of the container is definitely
2526
                // after the range boundary.
2527
                var nodeIndex = end;
2528
2529
                while (true) {
2530
                    if (nodeIndex == childNodeCount) {
2531
                        containerElement.appendChild(workingNode);
2532
                    } else {
2533
                        containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
2534
                    }
2535
                    workingRange.moveToElementText(workingNode);
2536
                    comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
2537
                    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...
2538
                        break;
2539
                    } else if (comparison == -1) {
2540
                        if (end == start + 1) {
2541
                            // We know the endth child node is after the range boundary, so we must be done.
2542
                            break;
2543
                        } 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...
2544
                            start = nodeIndex;
2545
                        }
2546
                    } else {
2547
                        end = (end == start + 1) ? start : nodeIndex;
2548
                    }
2549
                    nodeIndex = Math.floor((start + end) / 2);
2550
                    containerElement.removeChild(workingNode);
2551
                }
2552
2553
2554
                // We've now reached or gone past the boundary of the text range we're interested in
2555
                // so have identified the node we want
2556
                boundaryNode = workingNode.nextSibling;
2557
2558
                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 2529 is not entered. Are you sure this can never be the case?
Loading history...
2559
                    // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
2560
                    // the node containing the text range's boundary, so we move the end of the working range to the
2561
                    // boundary point and measure the length of its text to get the boundary's offset within the node.
2562
                    workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
2563
2564
                    var offset;
2565
2566
                    if (/[\r\n]/.test(boundaryNode.data)) {
2567
                        /*
2568
                        For the particular case of a boundary within a text node containing rendered line breaks (within a
2569
                        <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
2570
                        IE. The facts:
2571
                        
2572
                        - Each line break is represented as \r in the text node's data/nodeValue properties
2573
                        - Each line break is represented as \r\n in the TextRange's 'text' property
2574
                        - The 'text' property of the TextRange does not contain trailing line breaks
2575
                        
2576
                        To get round the problem presented by the final fact above, we can use the fact that TextRange's
2577
                        moveStart() and moveEnd() methods return the actual number of characters moved, which is not
2578
                        necessarily the same as the number of characters it was instructed to move. The simplest approach is
2579
                        to use this to store the characters moved when moving both the start and end of the range to the
2580
                        start of the document body and subtracting the start offset from the end offset (the
2581
                        "move-negative-gazillion" method). However, this is extremely slow when the document is large and
2582
                        the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
2583
                        the end of the document) has the same problem.
2584
                        
2585
                        Another approach that works is to use moveStart() to move the start boundary of the range up to the
2586
                        end boundary one character at a time and incrementing a counter with the value returned by the
2587
                        moveStart() call. However, the check for whether the start boundary has reached the end boundary is
2588
                        expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
2589
                        by the location of the range within the document).
2590
                        
2591
                        The approach used below is a hybrid of the two methods above. It uses the fact that a string
2592
                        containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
2593
                        be longer than the text of the TextRange, so the start of the range is moved that length initially
2594
                        and then a character at a time to make up for any trailing line breaks not contained in the 'text'
2595
                        property. This has good performance in most situations compared to the previous two methods.
2596
                        */
2597
                        var tempRange = workingRange.duplicate();
2598
                        var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
2599
2600
                        offset = tempRange.moveStart("character", rangeLength);
2601
                        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...
2602
                            offset++;
2603
                            tempRange.moveStart("character", 1);
2604
                        }
2605
                    } else {
2606
                        offset = workingRange.text.length;
2607
                    }
2608
                    boundaryPosition = new DomPosition(boundaryNode, offset);
2609
                } else {
2610
2611
                    // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2612
                    // a position within that, and likewise for a start boundary preceding a character data node
2613
                    previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2614
                    nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2615
                    if (nextNode && isCharacterDataNode(nextNode)) {
2616
                        boundaryPosition = new DomPosition(nextNode, 0);
2617
                    } else if (previousNode && isCharacterDataNode(previousNode)) {
2618
                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
2619
                    } else {
2620
                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2621
                    }
2622
                }
2623
2624
                // Clean up
2625
                workingNode.parentNode.removeChild(workingNode);
2626
2627
                return {
2628
                    boundaryPosition: boundaryPosition,
2629
                    nodeInfo: {
2630
                        nodeIndex: nodeIndex,
2631
                        containerElement: containerElement
2632
                    }
2633
                };
2634
            };
2635
2636
            // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
2637
            // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2638
            // (http://code.google.com/p/ierange/)
2639
            var createBoundaryTextRange = function(boundaryPosition, isStart) {
2640
                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2641
                var doc = dom.getDocument(boundaryPosition.node);
2642
                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
2643
                var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
2644
2645
                if (nodeIsDataNode) {
2646
                    boundaryNode = boundaryPosition.node;
2647
                    boundaryParent = boundaryNode.parentNode;
2648
                } else {
2649
                    childNodes = boundaryPosition.node.childNodes;
2650
                    boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2651
                    boundaryParent = boundaryPosition.node;
2652
                }
2653
2654
                // Position the range immediately before the node containing the boundary
2655
                workingNode = doc.createElement("span");
2656
2657
                // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
2658
                // the element rather than immediately before or after it
2659
                workingNode.innerHTML = "&#feff;";
2660
2661
                // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2662
                // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2663
                if (boundaryNode) {
2664
                    boundaryParent.insertBefore(workingNode, boundaryNode);
2665
                } else {
2666
                    boundaryParent.appendChild(workingNode);
2667
                }
2668
2669
                workingRange.moveToElementText(workingNode);
2670
                workingRange.collapse(!isStart);
2671
2672
                // Clean up
2673
                boundaryParent.removeChild(workingNode);
2674
2675
                // Move the working range to the text offset, if required
2676
                if (nodeIsDataNode) {
2677
                    workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2678
                }
2679
2680
                return workingRange;
2681
            };
2682
2683
            /*------------------------------------------------------------------------------------------------------------*/
2684
2685
            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
2686
            // prototype
2687
2688
            WrappedTextRange = function(textRange) {
2689
                this.textRange = textRange;
2690
                this.refresh();
2691
            };
2692
2693
            WrappedTextRange.prototype = new DomRange(document);
2694
2695
            WrappedTextRange.prototype.refresh = function() {
2696
                var start, end, startBoundary;
2697
2698
                // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
2699
                var rangeContainerElement = getTextRangeContainerElement(this.textRange);
2700
2701
                if (textRangeIsCollapsed(this.textRange)) {
2702
                    end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
2703
                        true).boundaryPosition;
2704
                } else {
2705
                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
2706
                    start = startBoundary.boundaryPosition;
2707
2708
                    // An optimization used here is that if the start and end boundaries have the same parent element, the
2709
                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes
2710
                    // the start boundary
2711
                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
2712
                        startBoundary.nodeInfo).boundaryPosition;
2713
                }
2714
2715
                this.setStart(start.node, start.offset);
2716
                this.setEnd(end.node, end.offset);
2717
            };
2718
2719
            WrappedTextRange.prototype.getName = function() {
2720
                return "WrappedTextRange";
2721
            };
2722
2723
            DomRange.copyComparisonConstants(WrappedTextRange);
2724
2725
            var rangeToTextRange = function(range) {
2726
                if (range.collapsed) {
2727
                    return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2728
                } 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...
2729
                    var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2730
                    var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
2731
                    var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
2732
                    textRange.setEndPoint("StartToStart", startRange);
2733
                    textRange.setEndPoint("EndToEnd", endRange);
2734
                    return textRange;
2735
                }
2736
            };
2737
2738
            WrappedTextRange.rangeToTextRange = rangeToTextRange;
2739
2740
            WrappedTextRange.prototype.toTextRange = function() {
2741
                return rangeToTextRange(this);
2742
            };
2743
2744
            api.WrappedTextRange = WrappedTextRange;
2745
2746
            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
2747
            // implementation to use by default.
2748
            if (!api.features.implementsDomRange || api.config.preferTextRange) {
2749
                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2750
                var globalObj = (function() { return this; })();
2751
                if (typeof globalObj.Range == "undefined") {
2752
                    globalObj.Range = WrappedTextRange;
2753
                }
2754
2755
                api.createNativeRange = function(doc) {
2756
                    doc = getContentDocument(doc, module, "createNativeRange");
2757
                    return getBody(doc).createTextRange();
2758
                };
2759
2760
                api.WrappedRange = WrappedTextRange;
2761
            }
2762
        }
2763
2764
        api.createRange = function(doc) {
2765
            doc = getContentDocument(doc, module, "createRange");
2766
            return new api.WrappedRange(api.createNativeRange(doc));
2767
        };
2768
2769
        api.createRangyRange = function(doc) {
2770
            doc = getContentDocument(doc, module, "createRangyRange");
2771
            return new DomRange(doc);
2772
        };
2773
2774
        api.createIframeRange = function(iframeEl) {
2775
            module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
2776
            return api.createRange(iframeEl);
2777
        };
2778
2779
        api.createIframeRangyRange = function(iframeEl) {
2780
            module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
2781
            return api.createRangyRange(iframeEl);
2782
        };
2783
2784
        api.addShimListener(function(win) {
2785
            var doc = win.document;
2786
            if (typeof doc.createRange == "undefined") {
2787
                doc.createRange = function() {
2788
                    return api.createRange(doc);
2789
                };
2790
            }
2791
            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...
2792
        });
2793
    });
2794
2795
    /*----------------------------------------------------------------------------------------------------------------*/
2796
2797
    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
2798
    // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
2799
    api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
2800
        api.config.checkSelectionRanges = true;
2801
2802
        var BOOLEAN = "boolean";
2803
        var NUMBER = "number";
2804
        var dom = api.dom;
2805
        var util = api.util;
2806
        var isHostMethod = util.isHostMethod;
2807
        var DomRange = api.DomRange;
2808
        var WrappedRange = api.WrappedRange;
2809
        var DOMException = api.DOMException;
2810
        var DomPosition = dom.DomPosition;
2811
        var getNativeSelection;
2812
        var selectionIsCollapsed;
2813
        var features = api.features;
2814
        var CONTROL = "Control";
2815
        var getDocument = dom.getDocument;
2816
        var getBody = dom.getBody;
2817
        var rangesEqual = DomRange.rangesEqual;
2818
2819
2820
        // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
2821
        // Boolean (true for backwards).
2822
        function isDirectionBackward(dir) {
2823
            return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
2824
        }
2825
2826
        function getWindow(win, methodName) {
2827
            if (!win) {
2828
                return window;
2829
            } else if (dom.isWindow(win)) {
2830
                return win;
2831
            } else if (win instanceof WrappedSelection) {
2832
                return win.win;
2833
            } else {
2834
                var doc = dom.getContentDocument(win, module, methodName);
2835
                return dom.getWindow(doc);
2836
            }
2837
        }
2838
2839
        function getWinSelection(winParam) {
2840
            return getWindow(winParam, "getWinSelection").getSelection();
2841
        }
2842
2843
        function getDocSelection(winParam) {
2844
            return getWindow(winParam, "getDocSelection").document.selection;
2845
        }
2846
        
2847
        function winSelectionIsBackward(sel) {
2848
            var backward = false;
2849
            if (sel.anchorNode) {
2850
                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...
2851
            }
2852
            return backward;
2853
        }
2854
2855
        // Test for the Range/TextRange and Selection features required
2856
        // Test for ability to retrieve selection
2857
        var implementsWinGetSelection = isHostMethod(window, "getSelection"),
2858
            implementsDocSelection = util.isHostObject(document, "selection");
2859
2860
        features.implementsWinGetSelection = implementsWinGetSelection;
2861
        features.implementsDocSelection = implementsDocSelection;
2862
2863
        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
2864
2865
        if (useDocumentSelection) {
2866
            getNativeSelection = getDocSelection;
2867
            api.isSelectionValid = function(winParam) {
2868
                var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
2869
2870
                // Check whether the selection TextRange is actually contained within the correct document
2871
                return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
2872
            };
2873
        } else if (implementsWinGetSelection) {
2874
            getNativeSelection = getWinSelection;
2875
            api.isSelectionValid = function() {
2876
                return true;
2877
            };
2878
        } else {
2879
            module.fail("Neither document.selection or window.getSelection() detected.");
2880
        }
2881
2882
        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...
2883
2884
        var testSelection = getNativeSelection();
2885
        var testRange = api.createNativeRange(document);
2886
        var body = getBody(document);
2887
2888
        // Obtaining a range from a selection
2889
        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
2890
            ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
2891
2892
        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
2893
2894
        // Test for existence of native selection extend() method
2895
        var selectionHasExtend = isHostMethod(testSelection, "extend");
2896
        features.selectionHasExtend = selectionHasExtend;
2897
        
2898
        // Test if rangeCount exists
2899
        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
2900
        features.selectionHasRangeCount = selectionHasRangeCount;
2901
2902
        var selectionSupportsMultipleRanges = false;
2903
        var collapsedNonEditableSelectionsSupported = true;
2904
2905
        var addRangeBackwardToNative = selectionHasExtend ?
2906
            function(nativeSelection, range) {
2907
                var doc = DomRange.getRangeDocument(range);
2908
                var endRange = api.createRange(doc);
2909
                endRange.collapseToPoint(range.endContainer, range.endOffset);
2910
                nativeSelection.addRange(getNativeRange(endRange));
2911
                nativeSelection.extend(range.startContainer, range.startOffset);
2912
            } : null;
2913
2914
        if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
2915
                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
2916
2917
            (function() {
2918
                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
2919
                // performed on the current document's selection. See issue 109.
2920
2921
                // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
2922
                // because initialization usually happens when the document loads, but could be a problem for a script that
2923
                // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
2924
                // selection.
2925
                var sel = window.getSelection();
2926
                if (sel) {
2927
                    // Store the current selection
2928
                    var originalSelectionRangeCount = sel.rangeCount;
2929
                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
2930
                    var originalSelectionRanges = [];
2931
                    var originalSelectionBackward = winSelectionIsBackward(sel); 
2932
                    for (var i = 0; i < originalSelectionRangeCount; ++i) {
2933
                        originalSelectionRanges[i] = sel.getRangeAt(i);
2934
                    }
2935
                    
2936
                    // Create some test elements
2937
                    var body = getBody(document);
2938
                    var testEl = body.appendChild( document.createElement("div") );
2939
                    testEl.contentEditable = "false";
2940
                    var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
2941
2942
                    // Test whether the native selection will allow a collapsed selection within a non-editable element
2943
                    var r1 = document.createRange();
2944
2945
                    r1.setStart(textNode, 1);
2946
                    r1.collapse(true);
2947
                    sel.addRange(r1);
2948
                    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...
2949
                    sel.removeAllRanges();
2950
2951
                    // Test whether the native selection is capable of supporting multiple ranges.
2952
                    if (!selectionHasMultipleRanges) {
2953
                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
2954
                        // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
2955
                        // nothing we can do about this while retaining the feature test so we have to resort to a browser
2956
                        // sniff. I'm not happy about it. See
2957
                        // https://code.google.com/p/chromium/issues/detail?id=399791
2958
                        var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
2959
                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
2960
                            selectionSupportsMultipleRanges = false;
2961
                        } else {
2962
                            var r2 = r1.cloneRange();
2963
                            r1.setStart(textNode, 0);
2964
                            r2.setEnd(textNode, 3);
2965
                            r2.setStart(textNode, 2);
2966
                            sel.addRange(r1);
2967
                            sel.addRange(r2);
2968
                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
2969
                        }
2970
                    }
2971
2972
                    // Clean up
2973
                    body.removeChild(testEl);
2974
                    sel.removeAllRanges();
2975
2976
                    for (i = 0; i < originalSelectionRangeCount; ++i) {
2977
                        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...
2978
                            if (addRangeBackwardToNative) {
2979
                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);
2980
                            } else {
2981
                                api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
2982
                                sel.addRange(originalSelectionRanges[i]);
2983
                            }
2984
                        } else {
2985
                            sel.addRange(originalSelectionRanges[i]);
2986
                        }
2987
                    }
2988
                }
2989
            })();
2990
        }
2991
2992
        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
2993
        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
2994
2995
        // ControlRanges
2996
        var implementsControlRange = false, testControlRange;
2997
2998
        if (body && isHostMethod(body, "createControlRange")) {
2999
            testControlRange = body.createControlRange();
3000
            if (util.areHostProperties(testControlRange, ["item", "add"])) {
3001
                implementsControlRange = true;
3002
            }
3003
        }
3004
        features.implementsControlRange = implementsControlRange;
3005
3006
        // Selection collapsedness
3007
        if (selectionHasAnchorAndFocus) {
3008
            selectionIsCollapsed = function(sel) {
3009
                return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
3010
            };
3011
        } else {
3012
            selectionIsCollapsed = function(sel) {
3013
                return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
3014
            };
3015
        }
3016
3017
        function updateAnchorAndFocusFromRange(sel, range, backward) {
3018
            var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
3019
            sel.anchorNode = range[anchorPrefix + "Container"];
3020
            sel.anchorOffset = range[anchorPrefix + "Offset"];
3021
            sel.focusNode = range[focusPrefix + "Container"];
3022
            sel.focusOffset = range[focusPrefix + "Offset"];
3023
        }
3024
3025
        function updateAnchorAndFocusFromNativeSelection(sel) {
3026
            var nativeSel = sel.nativeSelection;
3027
            sel.anchorNode = nativeSel.anchorNode;
3028
            sel.anchorOffset = nativeSel.anchorOffset;
3029
            sel.focusNode = nativeSel.focusNode;
3030
            sel.focusOffset = nativeSel.focusOffset;
3031
        }
3032
3033
        function updateEmptySelection(sel) {
3034
            sel.anchorNode = sel.focusNode = null;
3035
            sel.anchorOffset = sel.focusOffset = 0;
3036
            sel.rangeCount = 0;
3037
            sel.isCollapsed = true;
3038
            sel._ranges.length = 0;
3039
        }
3040
3041
        function getNativeRange(range) {
3042
            var nativeRange;
3043
            if (range instanceof DomRange) {
3044
                nativeRange = api.createNativeRange(range.getDocument());
3045
                nativeRange.setEnd(range.endContainer, range.endOffset);
3046
                nativeRange.setStart(range.startContainer, range.startOffset);
3047
            } else if (range instanceof WrappedRange) {
3048
                nativeRange = range.nativeRange;
3049
            } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
3050
                nativeRange = range;
3051
            }
3052
            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 3049 is false. Are you sure this can never be the case?
Loading history...
3053
        }
3054
3055
        function rangeContainsSingleElement(rangeNodes) {
3056
            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...
3057
                return false;
3058
            }
3059
            for (var i = 1, len = rangeNodes.length; i < len; ++i) {
3060
                if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
3061
                    return false;
3062
                }
3063
            }
3064
            return true;
3065
        }
3066
3067
        function getSingleElementFromRange(range) {
3068
            var nodes = range.getNodes();
3069
            if (!rangeContainsSingleElement(nodes)) {
3070
                throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
3071
            }
3072
            return nodes[0];
3073
        }
3074
3075
        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
3076
        function isTextRange(range) {
3077
            return !!range && typeof range.text != "undefined";
3078
        }
3079
3080
        function updateFromTextRange(sel, range) {
3081
            // Create a Range from the selected TextRange
3082
            var wrappedRange = new WrappedRange(range);
3083
            sel._ranges = [wrappedRange];
3084
3085
            updateAnchorAndFocusFromRange(sel, wrappedRange, false);
3086
            sel.rangeCount = 1;
3087
            sel.isCollapsed = wrappedRange.collapsed;
3088
        }
3089
3090
        function updateControlSelection(sel) {
3091
            // Update the wrapped selection based on what's now in the native selection
3092
            sel._ranges.length = 0;
3093
            if (sel.docSelection.type == "None") {
3094
                updateEmptySelection(sel);
3095
            } else {
3096
                var controlRange = sel.docSelection.createRange();
3097
                if (isTextRange(controlRange)) {
3098
                    // This case (where the selection type is "Control" and calling createRange() on the selection returns
3099
                    // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
3100
                    // ControlRange have been removed from the ControlRange and removed from the document.
3101
                    updateFromTextRange(sel, controlRange);
3102
                } else {
3103
                    sel.rangeCount = controlRange.length;
3104
                    var range, doc = getDocument(controlRange.item(0));
3105
                    for (var i = 0; i < sel.rangeCount; ++i) {
3106
                        range = api.createRange(doc);
3107
                        range.selectNode(controlRange.item(i));
3108
                        sel._ranges.push(range);
3109
                    }
3110
                    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...
3111
                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
3112
                }
3113
            }
3114
        }
3115
3116
        function addRangeToControlSelection(sel, range) {
3117
            var controlRange = sel.docSelection.createRange();
3118
            var rangeElement = getSingleElementFromRange(range);
3119
3120
            // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
3121
            // contained by the supplied range
3122
            var doc = getDocument(controlRange.item(0));
3123
            var newControlRange = getBody(doc).createControlRange();
3124
            for (var i = 0, len = controlRange.length; i < len; ++i) {
3125
                newControlRange.add(controlRange.item(i));
3126
            }
3127
            try {
3128
                newControlRange.add(rangeElement);
3129
            } catch (ex) {
3130
                throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
3131
            }
3132
            newControlRange.select();
3133
3134
            // Update the wrapped selection based on what's now in the native selection
3135
            updateControlSelection(sel);
3136
        }
3137
3138
        var getSelectionRangeAt;
3139
3140
        if (isHostMethod(testSelection, "getRangeAt")) {
3141
            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
3142
            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
3143
            // lesson to us all, especially me.
3144
            getSelectionRangeAt = function(sel, index) {
3145
                try {
3146
                    return sel.getRangeAt(index);
3147
                } catch (ex) {
3148
                    return null;
3149
                }
3150
            };
3151
        } else if (selectionHasAnchorAndFocus) {
3152
            getSelectionRangeAt = function(sel) {
3153
                var doc = getDocument(sel.anchorNode);
3154
                var range = api.createRange(doc);
3155
                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
3156
3157
                // Handle the case when the selection was selected backwards (from the end to the start in the
3158
                // document)
3159
                if (range.collapsed !== this.isCollapsed) {
3160
                    range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
3161
                }
3162
3163
                return range;
3164
            };
3165
        }
3166
3167
        function WrappedSelection(selection, docSelection, win) {
3168
            this.nativeSelection = selection;
3169
            this.docSelection = docSelection;
3170
            this._ranges = [];
3171
            this.win = win;
3172
            this.refresh();
3173
        }
3174
3175
        WrappedSelection.prototype = api.selectionPrototype;
3176
3177
        function deleteProperties(sel) {
3178
            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
3179
            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
3180
            sel.detached = true;
3181
        }
3182
3183
        var cachedRangySelections = [];
3184
3185
        function actOnCachedSelection(win, action) {
3186
            var i = cachedRangySelections.length, cached, sel;
3187
            while (i--) {
3188
                cached = cachedRangySelections[i];
3189
                sel = cached.selection;
3190
                if (action == "deleteAll") {
3191
                    deleteProperties(sel);
3192
                } else if (cached.win == win) {
3193
                    if (action == "delete") {
3194
                        cachedRangySelections.splice(i, 1);
3195
                        return true;
3196
                    } 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...
3197
                        return sel;
3198
                    }
3199
                }
3200
            }
3201
            if (action == "deleteAll") {
3202
                cachedRangySelections.length = 0;
3203
            }
3204
            return null;
3205
        }
3206
3207
        var getSelection = function(win) {
3208
            // Check if the parameter is a Rangy Selection object
3209
            if (win && win instanceof WrappedSelection) {
3210
                win.refresh();
3211
                return win;
3212
            }
3213
3214
            win = getWindow(win, "getNativeSelection");
3215
3216
            var sel = actOnCachedSelection(win);
3217
            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
3218
            if (sel) {
3219
                sel.nativeSelection = nativeSel;
3220
                sel.docSelection = docSel;
3221
                sel.refresh();
3222
            } else {
3223
                sel = new WrappedSelection(nativeSel, docSel, win);
3224
                cachedRangySelections.push( { win: win, selection: sel } );
3225
            }
3226
            return sel;
3227
        };
3228
3229
        api.getSelection = getSelection;
3230
3231
        api.getIframeSelection = function(iframeEl) {
3232
            module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
3233
            return api.getSelection(dom.getIframeWindow(iframeEl));
3234
        };
3235
3236
        var selProto = WrappedSelection.prototype;
3237
3238
        function createControlSelection(sel, ranges) {
3239
            // Ensure that the selection becomes of type "Control"
3240
            var doc = getDocument(ranges[0].startContainer);
3241
            var controlRange = getBody(doc).createControlRange();
3242
            for (var i = 0, el, len = ranges.length; i < len; ++i) {
3243
                el = getSingleElementFromRange(ranges[i]);
3244
                try {
3245
                    controlRange.add(el);
3246
                } catch (ex) {
3247
                    throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
3248
                }
3249
            }
3250
            controlRange.select();
3251
3252
            // Update the wrapped selection based on what's now in the native selection
3253
            updateControlSelection(sel);
3254
        }
3255
3256
        // Selecting a range
3257
        if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
3258
            selProto.removeAllRanges = function() {
3259
                this.nativeSelection.removeAllRanges();
3260
                updateEmptySelection(this);
3261
            };
3262
3263
            var addRangeBackward = function(sel, range) {
3264
                addRangeBackwardToNative(sel.nativeSelection, range);
3265
                sel.refresh();
3266
            };
3267
3268
            if (selectionHasRangeCount) {
3269
                selProto.addRange = function(range, direction) {
3270
                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3271
                        addRangeToControlSelection(this, range);
3272
                    } else {
3273
                        if (isDirectionBackward(direction) && selectionHasExtend) {
3274
                            addRangeBackward(this, range);
3275
                        } else {
3276
                            var previousRangeCount;
3277
                            if (selectionSupportsMultipleRanges) {
3278
                                previousRangeCount = this.rangeCount;
3279
                            } else {
3280
                                this.removeAllRanges();
3281
                                previousRangeCount = 0;
3282
                            }
3283
                            // Clone the native range so that changing the selected range does not affect the selection.
3284
                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See
3285
                            // issue 80.
3286
                            this.nativeSelection.addRange(getNativeRange(range).cloneRange());
3287
3288
                            // Check whether adding the range was successful
3289
                            this.rangeCount = this.nativeSelection.rangeCount;
3290
3291
                            if (this.rangeCount == previousRangeCount + 1) {
3292
                                // The range was added successfully
3293
3294
                                // Check whether the range that we added to the selection is reflected in the last range extracted from
3295
                                // the selection
3296
                                if (api.config.checkSelectionRanges) {
3297
                                    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...
3298
                                    if (nativeRange && !rangesEqual(nativeRange, range)) {
3299
                                        // Happens in WebKit with, for example, a selection placed at the start of a text node
3300
                                        range = new WrappedRange(nativeRange);
3301
                                    }
3302
                                }
3303
                                this._ranges[this.rangeCount - 1] = range;
3304
                                updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
3305
                                this.isCollapsed = selectionIsCollapsed(this);
3306
                            } else {
3307
                                // The range was not added successfully. The simplest thing is to refresh
3308
                                this.refresh();
3309
                            }
3310
                        }
3311
                    }
3312
                };
3313
            } else {
3314
                selProto.addRange = function(range, direction) {
3315
                    if (isDirectionBackward(direction) && selectionHasExtend) {
3316
                        addRangeBackward(this, range);
3317
                    } else {
3318
                        this.nativeSelection.addRange(getNativeRange(range));
3319
                        this.refresh();
3320
                    }
3321
                };
3322
            }
3323
3324
            selProto.setRanges = function(ranges) {
3325
                if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
3326
                    createControlSelection(this, ranges);
3327
                } else {
3328
                    this.removeAllRanges();
3329
                    for (var i = 0, len = ranges.length; i < len; ++i) {
3330
                        this.addRange(ranges[i]);
3331
                    }
3332
                }
3333
            };
3334
        } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
3335
                   implementsControlRange && useDocumentSelection) {
3336
3337
            selProto.removeAllRanges = function() {
3338
                // Added try/catch as fix for issue #21
3339
                try {
3340
                    this.docSelection.empty();
3341
3342
                    // Check for empty() not working (issue #24)
3343
                    if (this.docSelection.type != "None") {
3344
                        // Work around failure to empty a control selection by instead selecting a TextRange and then
3345
                        // calling empty()
3346
                        var doc;
3347
                        if (this.anchorNode) {
3348
                            doc = getDocument(this.anchorNode);
3349
                        } else if (this.docSelection.type == CONTROL) {
3350
                            var controlRange = this.docSelection.createRange();
3351
                            if (controlRange.length) {
3352
                                doc = getDocument( controlRange.item(0) );
3353
                            }
3354
                        }
3355
                        if (doc) {
3356
                            var textRange = getBody(doc).createTextRange();
3357
                            textRange.select();
3358
                            this.docSelection.empty();
3359
                        }
3360
                    }
3361
                } 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...
3362
                updateEmptySelection(this);
3363
            };
3364
3365
            selProto.addRange = function(range) {
3366
                if (this.docSelection.type == CONTROL) {
3367
                    addRangeToControlSelection(this, range);
3368
                } else {
3369
                    api.WrappedTextRange.rangeToTextRange(range).select();
3370
                    this._ranges[0] = range;
3371
                    this.rangeCount = 1;
3372
                    this.isCollapsed = this._ranges[0].collapsed;
3373
                    updateAnchorAndFocusFromRange(this, range, false);
3374
                }
3375
            };
3376
3377
            selProto.setRanges = function(ranges) {
3378
                this.removeAllRanges();
3379
                var rangeCount = ranges.length;
3380
                if (rangeCount > 1) {
3381
                    createControlSelection(this, ranges);
3382
                } else if (rangeCount) {
3383
                    this.addRange(ranges[0]);
3384
                }
3385
            };
3386
        } else {
3387
            module.fail("No means of selecting a Range or TextRange was found");
3388
            return false;
3389
        }
3390
3391
        selProto.getRangeAt = function(index) {
3392
            if (index < 0 || index >= this.rangeCount) {
3393
                throw new DOMException("INDEX_SIZE_ERR");
3394
            } else {
3395
                // Clone the range to preserve selection-range independence. See issue 80.
3396
                return this._ranges[index].cloneRange();
3397
            }
3398
        };
3399
3400
        var refreshSelection;
3401
3402
        if (useDocumentSelection) {
3403
            refreshSelection = function(sel) {
3404
                var range;
3405
                if (api.isSelectionValid(sel.win)) {
3406
                    range = sel.docSelection.createRange();
3407
                } else {
3408
                    range = getBody(sel.win.document).createTextRange();
3409
                    range.collapse(true);
3410
                }
3411
3412
                if (sel.docSelection.type == CONTROL) {
3413
                    updateControlSelection(sel);
3414
                } else if (isTextRange(range)) {
3415
                    updateFromTextRange(sel, range);
3416
                } else {
3417
                    updateEmptySelection(sel);
3418
                }
3419
            };
3420
        } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
3421
            refreshSelection = function(sel) {
3422
                if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
3423
                    updateControlSelection(sel);
3424
                } else {
3425
                    sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
3426
                    if (sel.rangeCount) {
3427
                        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3428
                            sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
3429
                        }
3430
                        updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
3431
                        sel.isCollapsed = selectionIsCollapsed(sel);
3432
                    } else {
3433
                        updateEmptySelection(sel);
3434
                    }
3435
                }
3436
            };
3437
        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
3438
            refreshSelection = function(sel) {
3439
                var range, nativeSel = sel.nativeSelection;
3440
                if (nativeSel.anchorNode) {
3441
                    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...
3442
                    sel._ranges = [range];
3443
                    sel.rangeCount = 1;
3444
                    updateAnchorAndFocusFromNativeSelection(sel);
3445
                    sel.isCollapsed = selectionIsCollapsed(sel);
3446
                } else {
3447
                    updateEmptySelection(sel);
3448
                }
3449
            };
3450
        } else {
3451
            module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
3452
            return false;
3453
        }
3454
3455
        selProto.refresh = function(checkForChanges) {
3456
            var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
3457
            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
3458
3459
            refreshSelection(this);
3460
            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...
3461
                // Check the range count first
3462
                var i = oldRanges.length;
3463
                if (i != this._ranges.length) {
3464
                    return true;
3465
                }
3466
3467
                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
3468
                // ranges after this
3469
                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
3470
                    return true;
3471
                }
3472
3473
                // Finally, compare each range in turn
3474
                while (i--) {
3475
                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {
3476
                        return true;
3477
                    }
3478
                }
3479
                return false;
3480
            }
3481
        };
3482
3483
        // Removal of a single range
3484
        var removeRangeManually = function(sel, range) {
3485
            var ranges = sel.getAllRanges();
3486
            sel.removeAllRanges();
3487
            for (var i = 0, len = ranges.length; i < len; ++i) {
3488
                if (!rangesEqual(range, ranges[i])) {
3489
                    sel.addRange(ranges[i]);
3490
                }
3491
            }
3492
            if (!sel.rangeCount) {
3493
                updateEmptySelection(sel);
3494
            }
3495
        };
3496
3497
        if (implementsControlRange && implementsDocSelection) {
3498
            selProto.removeRange = function(range) {
3499
                if (this.docSelection.type == CONTROL) {
3500
                    var controlRange = this.docSelection.createRange();
3501
                    var rangeElement = getSingleElementFromRange(range);
3502
3503
                    // Create a new ControlRange containing all the elements in the selected ControlRange minus the
3504
                    // element contained by the supplied range
3505
                    var doc = getDocument(controlRange.item(0));
3506
                    var newControlRange = getBody(doc).createControlRange();
3507
                    var el, removed = false;
3508
                    for (var i = 0, len = controlRange.length; i < len; ++i) {
3509
                        el = controlRange.item(i);
3510
                        if (el !== rangeElement || removed) {
3511
                            newControlRange.add(controlRange.item(i));
3512
                        } else {
3513
                            removed = true;
3514
                        }
3515
                    }
3516
                    newControlRange.select();
3517
3518
                    // Update the wrapped selection based on what's now in the native selection
3519
                    updateControlSelection(this);
3520
                } else {
3521
                    removeRangeManually(this, range);
3522
                }
3523
            };
3524
        } else {
3525
            selProto.removeRange = function(range) {
3526
                removeRangeManually(this, range);
3527
            };
3528
        }
3529
3530
        // Detecting if a selection is backward
3531
        var selectionIsBackward;
3532
        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
3533
            selectionIsBackward = winSelectionIsBackward;
3534
3535
            selProto.isBackward = function() {
3536
                return selectionIsBackward(this);
3537
            };
3538
        } else {
3539
            selectionIsBackward = selProto.isBackward = function() {
3540
                return false;
3541
            };
3542
        }
3543
3544
        // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
3545
        selProto.isBackwards = selProto.isBackward;
3546
3547
        // Selection stringifier
3548
        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
3549
        // The current spec does not yet define this method.
3550
        selProto.toString = function() {
3551
            var rangeTexts = [];
3552
            for (var i = 0, len = this.rangeCount; i < len; ++i) {
3553
                rangeTexts[i] = "" + this._ranges[i];
3554
            }
3555
            return rangeTexts.join("");
3556
        };
3557
3558
        function assertNodeInSameDocument(sel, node) {
3559
            if (sel.win.document != getDocument(node)) {
3560
                throw new DOMException("WRONG_DOCUMENT_ERR");
3561
            }
3562
        }
3563
3564
        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
3565
        selProto.collapse = function(node, offset) {
3566
            assertNodeInSameDocument(this, node);
3567
            var range = api.createRange(node);
3568
            range.collapseToPoint(node, offset);
3569
            this.setSingleRange(range);
3570
            this.isCollapsed = true;
3571
        };
3572
3573
        selProto.collapseToStart = function() {
3574
            if (this.rangeCount) {
3575
                var range = this._ranges[0];
3576
                this.collapse(range.startContainer, range.startOffset);
3577
            } else {
3578
                throw new DOMException("INVALID_STATE_ERR");
3579
            }
3580
        };
3581
3582
        selProto.collapseToEnd = function() {
3583
            if (this.rangeCount) {
3584
                var range = this._ranges[this.rangeCount - 1];
3585
                this.collapse(range.endContainer, range.endOffset);
3586
            } else {
3587
                throw new DOMException("INVALID_STATE_ERR");
3588
            }
3589
        };
3590
3591
        // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
3592
        // never used by Rangy.
3593
        selProto.selectAllChildren = function(node) {
3594
            assertNodeInSameDocument(this, node);
3595
            var range = api.createRange(node);
3596
            range.selectNodeContents(node);
3597
            this.setSingleRange(range);
3598
        };
3599
3600
        selProto.deleteFromDocument = function() {
3601
            // Sepcial behaviour required for IE's control selections
3602
            if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3603
                var controlRange = this.docSelection.createRange();
3604
                var element;
3605
                while (controlRange.length) {
3606
                    element = controlRange.item(0);
3607
                    controlRange.remove(element);
3608
                    element.parentNode.removeChild(element);
3609
                }
3610
                this.refresh();
3611
            } else if (this.rangeCount) {
3612
                var ranges = this.getAllRanges();
3613
                if (ranges.length) {
3614
                    this.removeAllRanges();
3615
                    for (var i = 0, len = ranges.length; i < len; ++i) {
3616
                        ranges[i].deleteContents();
3617
                    }
3618
                    // The spec says nothing about what the selection should contain after calling deleteContents on each
3619
                    // range. Firefox moves the selection to where the final selected range was, so we emulate that
3620
                    this.addRange(ranges[len - 1]);
3621
                }
3622
            }
3623
        };
3624
3625
        // The following are non-standard extensions
3626
        selProto.eachRange = function(func, returnValue) {
3627
            for (var i = 0, len = this._ranges.length; i < len; ++i) {
3628
                if ( func( this.getRangeAt(i) ) ) {
3629
                    return returnValue;
3630
                }
3631
            }
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...
3632
        };
3633
3634
        selProto.getAllRanges = function() {
3635
            var ranges = [];
3636
            this.eachRange(function(range) {
3637
                ranges.push(range);
3638
            });
3639
            return ranges;
3640
        };
3641
3642
        selProto.setSingleRange = function(range, direction) {
3643
            this.removeAllRanges();
3644
            this.addRange(range, direction);
3645
        };
3646
3647
        selProto.callMethodOnEachRange = function(methodName, params) {
3648
            var results = [];
3649
            this.eachRange( function(range) {
3650
                results.push( range[methodName].apply(range, params) );
3651
            } );
3652
            return results;
3653
        };
3654
        
3655
        function createStartOrEndSetter(isStart) {
3656
            return function(node, offset) {
3657
                var range;
3658
                if (this.rangeCount) {
3659
                    range = this.getRangeAt(0);
3660
                    range["set" + (isStart ? "Start" : "End")](node, offset);
3661
                } else {
3662
                    range = api.createRange(this.win.document);
3663
                    range.setStartAndEnd(node, offset);
3664
                }
3665
                this.setSingleRange(range, this.isBackward());
3666
            };
3667
        }
3668
3669
        selProto.setStart = createStartOrEndSetter(true);
3670
        selProto.setEnd = createStartOrEndSetter(false);
3671
        
3672
        // Add select() method to Range prototype. Any existing selection will be removed.
3673
        api.rangePrototype.select = function(direction) {
3674
            getSelection( this.getDocument() ).setSingleRange(this, direction);
3675
        };
3676
3677
        selProto.changeEachRange = function(func) {
3678
            var ranges = [];
3679
            var backward = this.isBackward();
3680
3681
            this.eachRange(function(range) {
3682
                func(range);
3683
                ranges.push(range);
3684
            });
3685
3686
            this.removeAllRanges();
3687
            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...
3688
                this.addRange(ranges[0], "backward");
3689
            } else {
3690
                this.setRanges(ranges);
3691
            }
3692
        };
3693
3694
        selProto.containsNode = function(node, allowPartial) {
3695
            return this.eachRange( function(range) {
3696
                return range.containsNode(node, allowPartial);
3697
            }, true ) || false;
3698
        };
3699
3700
        selProto.getBookmark = function(containerNode) {
3701
            return {
3702
                backward: this.isBackward(),
3703
                rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
3704
            };
3705
        };
3706
3707
        selProto.moveToBookmark = function(bookmark) {
3708
            var selRanges = [];
3709
            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
3710
                range = api.createRange(this.win);
3711
                range.moveToBookmark(rangeBookmark);
3712
                selRanges.push(range);
3713
            }
3714
            if (bookmark.backward) {
3715
                this.setSingleRange(selRanges[0], "backward");
3716
            } else {
3717
                this.setRanges(selRanges);
3718
            }
3719
        };
3720
3721
        selProto.toHtml = function() {
3722
            var rangeHtmls = [];
3723
            this.eachRange(function(range) {
3724
                rangeHtmls.push( DomRange.toHtml(range) );
3725
            });
3726
            return rangeHtmls.join("");
3727
        };
3728
3729
        if (features.implementsTextRange) {
3730
            selProto.getNativeTextRange = function() {
3731
                var sel, textRange;
0 ignored issues
show
Unused Code introduced by
The variable textRange seems to be never used. Consider removing it.
Loading history...
3732
                if ( (sel = this.docSelection) ) {
3733
                    var range = sel.createRange();
3734
                    if (isTextRange(range)) {
3735
                        return range;
3736
                    } 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...
3737
                        throw module.createError("getNativeTextRange: selection is a control selection"); 
3738
                    }
3739
                } else if (this.rangeCount > 0) {
3740
                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
3741
                } else {
3742
                    throw module.createError("getNativeTextRange: selection contains no range");
3743
                }
3744
            };
3745
        }
3746
3747
        function inspect(sel) {
3748
            var rangeInspects = [];
3749
            var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
3750
            var focus = new DomPosition(sel.focusNode, sel.focusOffset);
3751
            var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
3752
3753
            if (typeof sel.rangeCount != "undefined") {
3754
                for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3755
                    rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
3756
                }
3757
            }
3758
            return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
3759
                    ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
3760
        }
3761
3762
        selProto.getName = function() {
3763
            return "WrappedSelection";
3764
        };
3765
3766
        selProto.inspect = function() {
3767
            return inspect(this);
3768
        };
3769
3770
        selProto.detach = function() {
3771
            actOnCachedSelection(this.win, "delete");
3772
            deleteProperties(this);
3773
        };
3774
3775
        WrappedSelection.detachAll = function() {
3776
            actOnCachedSelection(null, "deleteAll");
3777
        };
3778
3779
        WrappedSelection.inspect = inspect;
3780
        WrappedSelection.isDirectionBackward = isDirectionBackward;
3781
3782
        api.Selection = WrappedSelection;
3783
3784
        api.selectionPrototype = selProto;
3785
3786
        api.addShimListener(function(win) {
3787
            if (typeof win.getSelection == "undefined") {
3788
                win.getSelection = function() {
3789
                    return getSelection(win);
3790
                };
3791
            }
3792
            win = null;
3793
        });
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...
3794
    });
3795
    
3796
3797
    /*----------------------------------------------------------------------------------------------------------------*/
3798
3799
    return api;
3800
}, this);;/**
3801
 * Selection save and restore module for Rangy.
3802
 * Saves and restores user selections using marker invisible elements in the DOM.
3803
 *
3804
 * Part of Rangy, a cross-browser JavaScript range and selection library
3805
 * http://code.google.com/p/rangy/
3806
 *
3807
 * Depends on Rangy core.
3808
 *
3809
 * Copyright 2014, Tim Down
3810
 * Licensed under the MIT license.
3811
 * Version: 1.3alpha.20140804
3812
 * Build date: 4 August 2014
3813
 */
3814
(function(factory, global) {
3815
    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...
3816
        // AMD. Register as an anonymous module with a dependency on Rangy.
3817
        define(["rangy"], factory);
3818
        /*
3819
         } else if (typeof exports == "object") {
3820
         // Node/CommonJS style for Browserify
3821
         module.exports = factory;
3822
         */
3823
    } else {
3824
        // No AMD or CommonJS support so we use the rangy global variable
3825
        factory(global.rangy);
3826
    }
3827
})(function(rangy) {
3828
    rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
3829
        var dom = api.dom;
3830
3831
        var markerTextChar = "\ufeff";
3832
3833
        function gEBI(id, doc) {
3834
            return (doc || document).getElementById(id);
3835
        }
3836
3837
        function insertRangeBoundaryMarker(range, atStart) {
3838
            var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
3839
            var markerEl;
3840
            var doc = dom.getDocument(range.startContainer);
3841
3842
            // Clone the Range and collapse to the appropriate boundary point
3843
            var boundaryRange = range.cloneRange();
3844
            boundaryRange.collapse(atStart);
3845
3846
            // Create the marker element containing a single invisible character using DOM methods and insert it
3847
            markerEl = doc.createElement("span");
3848
            markerEl.id = markerId;
3849
            markerEl.style.lineHeight = "0";
3850
            markerEl.style.display = "none";
3851
            markerEl.className = "rangySelectionBoundary";
3852
            markerEl.appendChild(doc.createTextNode(markerTextChar));
3853
3854
            boundaryRange.insertNode(markerEl);
3855
            return markerEl;
3856
        }
3857
3858
        function setRangeBoundary(doc, range, markerId, atStart) {
3859
            var markerEl = gEBI(markerId, doc);
3860
            if (markerEl) {
3861
                range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
3862
                markerEl.parentNode.removeChild(markerEl);
3863
            } else {
3864
                module.warn("Marker element has been removed. Cannot restore selection.");
3865
            }
3866
        }
3867
3868
        function compareRanges(r1, r2) {
3869
            return r2.compareBoundaryPoints(r1.START_TO_START, r1);
3870
        }
3871
3872
        function saveRange(range, backward) {
3873
            var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
3874
3875
            if (range.collapsed) {
3876
                endEl = insertRangeBoundaryMarker(range, false);
3877
                return {
3878
                    document: doc,
3879
                    markerId: endEl.id,
3880
                    collapsed: true
3881
                };
3882
            } 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...
3883
                endEl = insertRangeBoundaryMarker(range, false);
3884
                startEl = insertRangeBoundaryMarker(range, true);
3885
3886
                return {
3887
                    document: doc,
3888
                    startMarkerId: startEl.id,
3889
                    endMarkerId: endEl.id,
3890
                    collapsed: false,
3891
                    backward: backward,
3892
                    toString: function() {
3893
                        return "original text: '" + text + "', new text: '" + range.toString() + "'";
3894
                    }
3895
                };
3896
            }
3897
        }
3898
3899
        function restoreRange(rangeInfo, normalize) {
3900
            var doc = rangeInfo.document;
3901
            if (typeof normalize == "undefined") {
3902
                normalize = true;
3903
            }
3904
            var range = api.createRange(doc);
3905
            if (rangeInfo.collapsed) {
3906
                var markerEl = gEBI(rangeInfo.markerId, doc);
3907
                if (markerEl) {
3908
                    markerEl.style.display = "inline";
3909
                    var previousNode = markerEl.previousSibling;
3910
3911
                    // Workaround for issue 17
3912
                    if (previousNode && previousNode.nodeType == 3) {
3913
                        markerEl.parentNode.removeChild(markerEl);
3914
                        range.collapseToPoint(previousNode, previousNode.length);
3915
                    } else {
3916
                        range.collapseBefore(markerEl);
3917
                        markerEl.parentNode.removeChild(markerEl);
3918
                    }
3919
                } else {
3920
                    module.warn("Marker element has been removed. Cannot restore selection.");
3921
                }
3922
            } else {
3923
                setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
3924
                setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
3925
            }
3926
3927
            if (normalize) {
3928
                range.normalizeBoundaries();
3929
            }
3930
3931
            return range;
3932
        }
3933
3934
        function saveRanges(ranges, backward) {
3935
            var rangeInfos = [], range, doc;
3936
3937
            // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
3938
            ranges = ranges.slice(0);
3939
            ranges.sort(compareRanges);
3940
3941
            for (var i = 0, len = ranges.length; i < len; ++i) {
3942
                rangeInfos[i] = saveRange(ranges[i], backward);
3943
            }
3944
3945
            // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
3946
            // between its markers
3947
            for (i = len - 1; i >= 0; --i) {
3948
                range = ranges[i];
3949
                doc = api.DomRange.getRangeDocument(range);
3950
                if (range.collapsed) {
3951
                    range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
3952
                } else {
3953
                    range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
3954
                    range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
3955
                }
3956
            }
3957
3958
            return rangeInfos;
3959
        }
3960
3961
        function saveSelection(win) {
3962
            if (!api.isSelectionValid(win)) {
3963
                module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
3964
                return null;
3965
            }
3966
            var sel = api.getSelection(win);
3967
            var ranges = sel.getAllRanges();
3968
            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...
3969
3970
            var rangeInfos = saveRanges(ranges, backward);
3971
3972
            // Ensure current selection is unaffected
3973
            if (backward) {
3974
                sel.setSingleRange(ranges[0], "backward");
3975
            } else {
3976
                sel.setRanges(ranges);
3977
            }
3978
3979
            return {
3980
                win: win,
3981
                rangeInfos: rangeInfos,
3982
                restored: false
3983
            };
3984
        }
3985
3986
        function restoreRanges(rangeInfos) {
3987
            var ranges = [];
3988
3989
            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
3990
            // normalization affecting previously restored ranges.
3991
            var rangeCount = rangeInfos.length;
3992
3993
            for (var i = rangeCount - 1; i >= 0; i--) {
3994
                ranges[i] = restoreRange(rangeInfos[i], true);
3995
            }
3996
3997
            return ranges;
3998
        }
3999
4000
        function restoreSelection(savedSelection, preserveDirection) {
4001
            if (!savedSelection.restored) {
4002
                var rangeInfos = savedSelection.rangeInfos;
4003
                var sel = api.getSelection(savedSelection.win);
4004
                var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
4005
4006
                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...
4007
                    sel.removeAllRanges();
4008
                    sel.addRange(ranges[0], true);
4009
                } else {
4010
                    sel.setRanges(ranges);
4011
                }
4012
4013
                savedSelection.restored = true;
4014
            }
4015
        }
4016
4017
        function removeMarkerElement(doc, markerId) {
4018
            var markerEl = gEBI(markerId, doc);
4019
            if (markerEl) {
4020
                markerEl.parentNode.removeChild(markerEl);
4021
            }
4022
        }
4023
4024
        function removeMarkers(savedSelection) {
4025
            var rangeInfos = savedSelection.rangeInfos;
4026
            for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
4027
                rangeInfo = rangeInfos[i];
4028
                if (rangeInfo.collapsed) {
4029
                    removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
4030
                } else {
4031
                    removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
4032
                    removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
4033
                }
4034
            }
4035
        }
4036
4037
        api.util.extend(api, {
4038
            saveRange: saveRange,
4039
            restoreRange: restoreRange,
4040
            saveRanges: saveRanges,
4041
            restoreRanges: restoreRanges,
4042
            saveSelection: saveSelection,
4043
            restoreSelection: restoreSelection,
4044
            removeMarkerElement: removeMarkerElement,
4045
            removeMarkers: removeMarkers
4046
        });
4047
    });
4048
    
4049
}, this);;/*
4050
	Base.js, version 1.1a
4051
	Copyright 2006-2010, Dean Edwards
4052
	License: http://www.opensource.org/licenses/mit-license.php
4053
*/
4054
4055
var Base = function() {
4056
	// dummy
4057
};
4058
4059
Base.extend = function(_instance, _static) { // subclass
4060
	var extend = Base.prototype.extend;
4061
	
4062
	// build the prototype
4063
	Base._prototyping = true;
4064
	var proto = new this;
4065
	extend.call(proto, _instance);
4066
  proto.base = function() {
4067
    // call this method from any other method to invoke that method's ancestor
4068
  };
4069
	delete Base._prototyping;
4070
	
4071
	// create the wrapper for the constructor function
4072
	//var constructor = proto.constructor.valueOf(); //-dean
4073
	var constructor = proto.constructor;
4074
	var klass = proto.constructor = function() {
4075
		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...
4076
			if (this._constructing || this.constructor == klass) { // instantiation
4077
				this._constructing = true;
4078
				constructor.apply(this, arguments);
4079
				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...
4080
			} 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...
4081
				return (arguments[0].extend || extend).call(arguments[0], proto);
4082
			}
4083
		}
4084
	};
4085
	
4086
	// build the class interface
4087
	klass.ancestor = this;
4088
	klass.extend = this.extend;
4089
	klass.forEach = this.forEach;
4090
	klass.implement = this.implement;
4091
	klass.prototype = proto;
4092
	klass.toString = this.toString;
4093
	klass.valueOf = function(type) {
4094
		//return (type == "object") ? klass : constructor; //-dean
4095
		return (type == "object") ? klass : constructor.valueOf();
4096
	};
4097
	extend.call(klass, _static);
4098
	// class initialisation
4099
	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...
4100
	return klass;
4101
};
4102
4103
Base.prototype = {	
4104
	extend: function(source, value) {
4105
		if (arguments.length > 1) { // extending with a name/value pair
4106
			var ancestor = this[source];
4107
			if (ancestor && (typeof value == "function") && // overriding a method?
4108
				// the valueOf() comparison is to avoid circular references
4109
				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
4110
				/\bbase\b/.test(value)) {
4111
				// get the underlying method
4112
				var method = value.valueOf();
4113
				// override
4114
				value = function() {
4115
					var previous = this.base || Base.prototype.base;
4116
					this.base = ancestor;
4117
					var returnValue = method.apply(this, arguments);
4118
					this.base = previous;
4119
					return returnValue;
4120
				};
4121
				// point to the underlying method
4122
				value.valueOf = function(type) {
4123
					return (type == "object") ? value : method;
4124
				};
4125
				value.toString = Base.toString;
4126
			}
4127
			this[source] = value;
4128
		} else if (source) { // extending with an object literal
4129
			var extend = Base.prototype.extend;
4130
			// if this object has a customised extend method then use it
4131
			if (!Base._prototyping && typeof this != "function") {
4132
				extend = this.extend || extend;
4133
			}
4134
			var proto = {toSource: null};
4135
			// do the "toString" and other methods manually
4136
			var hidden = ["constructor", "toString", "valueOf"];
4137
			// if we are prototyping then include the constructor
4138
			var i = Base._prototyping ? 0 : 1;
4139
			while (key = hidden[i++]) {
4140
				if (source[key] != proto[key]) {
4141
					extend.call(this, key, source[key]);
4142
4143
				}
4144
			}
4145
			// copy each of the source object's properties to this object
4146
			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...
4147
				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...
4148
			}
4149
		}
4150
		return this;
4151
	}
4152
};
4153
4154
// initialise
4155
Base = Base.extend({
4156
	constructor: function() {
4157
		this.extend(arguments[0]);
4158
	}
4159
}, {
4160
	ancestor: Object,
4161
	version: "1.1",
4162
	
4163
	forEach: function(object, block, context) {
4164
		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...
4165
			if (this.prototype[key] === undefined) {
4166
				block.call(context, object[key], key, object);
4167
			}
4168
		}
4169
	},
4170
		
4171
	implement: function() {
4172
		for (var i = 0; i < arguments.length; i++) {
4173
			if (typeof arguments[i] == "function") {
4174
				// if it's a function, call it
4175
				arguments[i](this.prototype);
4176
			} else {
4177
				// add the interface using the extend method
4178
				this.prototype.extend(arguments[i]);
4179
			}
4180
		}
4181
		return this;
4182
	},
4183
	
4184
	toString: function() {
4185
		return String(this.valueOf());
4186
	}
4187
});;/**
4188
 * Detect browser support for specific features
4189
 */
4190
wysihtml5.browser = (function() {
4191
  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...
4192
      testElement = document.createElement("div"),
4193
      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
4194
      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1,
4195
      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1,
4196
      isChrome    = userAgent.indexOf("Chrome/")      !== -1,
4197
      isOpera     = userAgent.indexOf("Opera/")       !== -1;
4198
4199
  function iosVersion(userAgent) {
4200
    return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
4201
  }
4202
4203
  function androidVersion(userAgent) {
4204
    return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
4205
  }
4206
4207
  function isIE(version, equation) {
4208
    var rv = -1,
4209
        re;
4210
4211
    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...
4212
      re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
4213
    } else if (navigator.appName == 'Netscape') {
4214
      re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
4215
    }
4216
4217
    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...
4218
      rv = parseFloat(RegExp.$1);
4219
    }
4220
4221
    if (rv === -1) { return false; }
4222
    if (!version) { return true; }
4223
    if (!equation) { return version === rv; }
4224
    if (equation === "<") { return version < rv; }
4225
    if (equation === ">") { return version > rv; }
4226
    if (equation === "<=") { return version <= rv; }
4227
    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...
4228
  }
4229
4230
  return {
4231
    // Static variable needed, publicly accessible, to be able override it in unit tests
4232
    USER_AGENT: userAgent,
4233
4234
    /**
4235
     * Exclude browsers that are not capable of displaying and handling
4236
     * contentEditable as desired:
4237
     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
4238
     *    - IE < 8 create invalid markup and crash randomly from time to time
4239
     *
4240
     * @return {Boolean}
4241
     */
4242
    supported: function() {
4243
      var userAgent                   = this.USER_AGENT.toLowerCase(),
4244
          // Essential for making html elements editable
4245
          hasContentEditableSupport   = "contentEditable" in testElement,
4246
          // Following methods are needed in order to interact with the contentEditable area
4247
          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
4248
          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
4249
          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
4250
          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
4251
          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
4252
      return hasContentEditableSupport
4253
        && hasEditingApiSupport
4254
        && hasQuerySelectorSupport
4255
        && !isIncompatibleMobileBrowser;
4256
    },
4257
4258
    isTouchDevice: function() {
4259
      return this.supportsEvent("touchmove");
4260
    },
4261
4262
    isIos: function() {
4263
      return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
4264
    },
4265
4266
    isAndroid: function() {
4267
      return this.USER_AGENT.indexOf("Android") !== -1;
4268
    },
4269
4270
    /**
4271
     * Whether the browser supports sandboxed iframes
4272
     * Currently only IE 6+ offers such feature <iframe security="restricted">
4273
     *
4274
     * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
4275
     * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
4276
     *
4277
     * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
4278
     */
4279
    supportsSandboxedIframes: function() {
4280
      return isIE();
4281
    },
4282
4283
    /**
4284
     * IE6+7 throw a mixed content warning when the src of an iframe
4285
     * is empty/unset or about:blank
4286
     * window.querySelector is implemented as of IE8
4287
     */
4288
    throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
4289
      return !("querySelector" in document);
4290
    },
4291
4292
    /**
4293
     * Whether the caret is correctly displayed in contentEditable elements
4294
     * Firefox sometimes shows a huge caret in the beginning after focusing
4295
     */
4296
    displaysCaretInEmptyContentEditableCorrectly: function() {
4297
      return isIE();
4298
    },
4299
4300
    /**
4301
     * Opera and IE are the only browsers who offer the css value
4302
     * in the original unit, thx to the currentStyle object
4303
     * All other browsers provide the computed style in px via window.getComputedStyle
4304
     */
4305
    hasCurrentStyleProperty: function() {
4306
      return "currentStyle" in testElement;
4307
    },
4308
4309
    /**
4310
     * Firefox on OSX navigates through history when hitting CMD + Arrow right/left
4311
     */
4312
    hasHistoryIssue: function() {
4313
      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...
4314
    },
4315
4316
    /**
4317
     * Whether the browser inserts a <br> when pressing enter in a contentEditable element
4318
     */
4319
    insertsLineBreaksOnReturn: function() {
4320
      return isGecko;
4321
    },
4322
4323
    supportsPlaceholderAttributeOn: function(element) {
4324
      return "placeholder" in element;
4325
    },
4326
4327
    supportsEvent: function(eventName) {
4328
      return "on" + eventName in testElement || (function() {
4329
        testElement.setAttribute("on" + eventName, "return;");
4330
        return typeof(testElement["on" + eventName]) === "function";
4331
      })();
4332
    },
4333
4334
    /**
4335
     * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
4336
     */
4337
    supportsEventsInIframeCorrectly: function() {
4338
      return !isOpera;
4339
    },
4340
4341
    /**
4342
     * Everything below IE9 doesn't know how to treat HTML5 tags
4343
     *
4344
     * @param {Object} context The document object on which to check HTML5 support
4345
     *
4346
     * @example
4347
     *    wysihtml5.browser.supportsHTML5Tags(document);
4348
     */
4349
    supportsHTML5Tags: function(context) {
4350
      var element = context.createElement("div"),
4351
          html5   = "<article>foo</article>";
4352
      element.innerHTML = html5;
4353
      return element.innerHTML.toLowerCase() === html5;
4354
    },
4355
4356
    /**
4357
     * Checks whether a document supports a certain queryCommand
4358
     * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
4359
     * in oder to report correct results
4360
     *
4361
     * @param {Object} doc Document object on which to check for a query command
4362
     * @param {String} command The query command to check for
4363
     * @return {Boolean}
4364
     *
4365
     * @example
4366
     *    wysihtml5.browser.supportsCommand(document, "bold");
4367
     */
4368
    supportsCommand: (function() {
4369
      // Following commands are supported but contain bugs in some browsers
4370
      var buggyCommands = {
4371
        // formatBlock fails with some tags (eg. <blockquote>)
4372
        "formatBlock":          isIE(10, "<="),
4373
         // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
4374
         // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
4375
         // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
4376
        "insertUnorderedList":  isIE(),
4377
        "insertOrderedList":    isIE()
4378
      };
4379
4380
      // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
4381
      var supported = {
4382
        "insertHTML": isGecko
4383
      };
4384
4385
      return function(doc, command) {
4386
        var isBuggy = buggyCommands[command];
4387
        if (!isBuggy) {
4388
          // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
4389
          try {
4390
            return doc.queryCommandSupported(command);
4391
          } 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...
4392
4393
          try {
4394
            return doc.queryCommandEnabled(command);
4395
          } catch(e2) {
4396
            return !!supported[command];
4397
          }
4398
        }
4399
        return false;
4400
      };
4401
    })(),
4402
4403
    /**
4404
     * IE: URLs starting with:
4405
     *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
4406
     *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
4407
     * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
4408
     * space bar when the caret is directly after such an url.
4409
     * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
4410
     * (related blog post on msdn
4411
     * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
4412
     */
4413
    doesAutoLinkingInContentEditable: function() {
4414
      return isIE();
4415
    },
4416
4417
    /**
4418
     * As stated above, IE auto links urls typed into contentEditable elements
4419
     * Since IE9 it's possible to prevent this behavior
4420
     */
4421
    canDisableAutoLinking: function() {
4422
      return this.supportsCommand(document, "AutoUrlDetect");
4423
    },
4424
4425
    /**
4426
     * IE leaves an empty paragraph in the contentEditable element after clearing it
4427
     * Chrome/Safari sometimes an empty <div>
4428
     */
4429
    clearsContentEditableCorrectly: function() {
4430
      return isGecko || isOpera || isWebKit;
4431
    },
4432
4433
    /**
4434
     * IE gives wrong results for getAttribute
4435
     */
4436
    supportsGetAttributeCorrectly: function() {
4437
      var td = document.createElement("td");
4438
      return td.getAttribute("rowspan") != "1";
4439
    },
4440
4441
    /**
4442
     * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
4443
     * Chrome and Safari both don't support this
4444
     */
4445
    canSelectImagesInContentEditable: function() {
4446
      return isGecko || isIE() || isOpera;
4447
    },
4448
4449
    /**
4450
     * All browsers except Safari and Chrome automatically scroll the range/caret position into view
4451
     */
4452
    autoScrollsToCaret: function() {
4453
      return !isWebKit;
4454
    },
4455
4456
    /**
4457
     * Check whether the browser automatically closes tags that don't need to be opened
4458
     */
4459
    autoClosesUnclosedTags: function() {
4460
      var clonedTestElement = testElement.cloneNode(false),
4461
          returnValue,
4462
          innerHTML;
4463
4464
      clonedTestElement.innerHTML = "<p><div></div>";
4465
      innerHTML                   = clonedTestElement.innerHTML.toLowerCase();
4466
      returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
4467
4468
      // Cache result by overwriting current function
4469
      this.autoClosesUnclosedTags = function() { return returnValue; };
4470
4471
      return returnValue;
4472
    },
4473
4474
    /**
4475
     * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
4476
     */
4477
    supportsNativeGetElementsByClassName: function() {
4478
      return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
4479
    },
4480
4481
    /**
4482
     * As of now (19.04.2011) only supported by Firefox 4 and Chrome
4483
     * See https://developer.mozilla.org/en/DOM/Selection/modify
4484
     */
4485
    supportsSelectionModify: function() {
4486
      return "getSelection" in window && "modify" in window.getSelection();
4487
    },
4488
4489
    /**
4490
     * Opera needs a white space after a <br> in order to position the caret correctly
4491
     */
4492
    needsSpaceAfterLineBreak: function() {
4493
      return isOpera;
4494
    },
4495
4496
    /**
4497
     * Whether the browser supports the speech api on the given element
4498
     * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
4499
     *
4500
     * @example
4501
     *    var input = document.createElement("input");
4502
     *    if (wysihtml5.browser.supportsSpeechApiOn(input)) {
4503
     *      // ...
4504
     *    }
4505
     */
4506
    supportsSpeechApiOn: function(input) {
4507
      var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
4508
      return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
4509
    },
4510
4511
    /**
4512
     * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
4513
     * See https://connect.microsoft.com/ie/feedback/details/650112
4514
     * or try the POC http://tifftiff.de/ie9_crash/
4515
     */
4516
    crashesWhenDefineProperty: function(property) {
4517
      return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
4518
    },
4519
4520
    /**
4521
     * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
4522
     */
4523
    doesAsyncFocus: function() {
4524
      return isIE();
4525
    },
4526
4527
    /**
4528
     * 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
4529
     */
4530
    hasProblemsSettingCaretAfterImg: function() {
4531
      return isIE();
4532
    },
4533
4534
    hasUndoInContextMenu: function() {
4535
      return isGecko || isChrome || isOpera;
4536
    },
4537
4538
    /**
4539
     * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
4540
     * is used (regardless if rangy or native)
4541
     * This especially happens when the caret is positioned right after a <br> because then
4542
     * insertNode() will insert the node right before the <br>
4543
     */
4544
    hasInsertNodeIssue: function() {
4545
      return isOpera;
4546
    },
4547
4548
    /**
4549
     * 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>)
4550
     */
4551
    hasIframeFocusIssue: function() {
4552
      return isIE();
4553
    },
4554
4555
    /**
4556
     * Chrome + Safari create invalid nested markup after paste
4557
     *
4558
     *  <p>
4559
     *    foo
4560
     *    <p>bar</p> <!-- BOO! -->
4561
     *  </p>
4562
     */
4563
    createsNestedInvalidMarkupAfterPaste: function() {
4564
      return isWebKit;
4565
    },
4566
4567
    supportsMutationEvents: function() {
4568
        return ("MutationEvent" in window);
4569
    }
4570
  };
4571
})();
4572
;wysihtml5.lang.array = function(arr) {
4573
  return {
4574
    /**
4575
     * Check whether a given object exists in an array
4576
     *
4577
     * @example
4578
     *    wysihtml5.lang.array([1, 2]).contains(1);
4579
     *    // => true
4580
     *
4581
     * Can be used to match array with array. If intersection is found true is returned
4582
     */
4583
    contains: function(needle) {
4584
      if (Array.isArray(needle)) {
4585
        for (var i = needle.length; i--;) {
4586
          if (wysihtml5.lang.array(arr).indexOf(needle[i]) !== -1) {
4587
            return true;
4588
          }
4589
        }
4590
        return false;
4591
      } 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...
4592
        return wysihtml5.lang.array(arr).indexOf(needle) !== -1;
4593
      }
4594
    },
4595
4596
    /**
4597
     * Check whether a given object exists in an array and return index
4598
     * If no elelemt found returns -1
4599
     *
4600
     * @example
4601
     *    wysihtml5.lang.array([1, 2]).indexOf(2);
4602
     *    // => 1
4603
     */
4604
    indexOf: function(needle) {
4605
        if (arr.indexOf) {
4606
          return arr.indexOf(needle);
4607
        } 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...
4608
          for (var i=0, length=arr.length; i<length; i++) {
4609
            if (arr[i] === needle) { return i; }
4610
          }
4611
          return -1;
4612
        }
4613
    },
4614
4615
    /**
4616
     * Substract one array from another
4617
     *
4618
     * @example
4619
     *    wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
4620
     *    // => [1, 2]
4621
     */
4622
    without: function(arrayToSubstract) {
4623
      arrayToSubstract = wysihtml5.lang.array(arrayToSubstract);
4624
      var newArr  = [],
4625
          i       = 0,
4626
          length  = arr.length;
4627
      for (; i<length; i++) {
4628
        if (!arrayToSubstract.contains(arr[i])) {
4629
          newArr.push(arr[i]);
4630
        }
4631
      }
4632
      return newArr;
4633
    },
4634
4635
    /**
4636
     * Return a clean native array
4637
     *
4638
     * Following will convert a Live NodeList to a proper Array
4639
     * @example
4640
     *    var childNodes = wysihtml5.lang.array(document.body.childNodes).get();
4641
     */
4642
    get: function() {
4643
      var i        = 0,
4644
          length   = arr.length,
4645
          newArray = [];
4646
      for (; i<length; i++) {
4647
        newArray.push(arr[i]);
4648
      }
4649
      return newArray;
4650
    },
4651
4652
    /**
4653
     * Creates a new array with the results of calling a provided function on every element in this array.
4654
     * optionally this can be provided as second argument
4655
     *
4656
     * @example
4657
     *    var childNodes = wysihtml5.lang.array([1,2,3,4]).map(function (value, index, array) {
4658
            return value * 2;
4659
     *    });
4660
     *    // => [2,4,6,8]
4661
     */
4662
    map: function(callback, thisArg) {
4663
      if (Array.prototype.map) {
4664
        return arr.map(callback, thisArg);
4665
      } 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...
4666
        var len = arr.length >>> 0,
4667
            A = new Array(len),
4668
            i = 0;
4669
        for (; i < len; i++) {
4670
           A[i] = callback.call(thisArg, arr[i], i, arr);
4671
        }
4672
        return A;
4673
      }
4674
    },
4675
4676
    /* ReturnS new array without duplicate entries
4677
     *
4678
     * @example
4679
     *    var uniq = wysihtml5.lang.array([1,2,3,2,1,4]).unique();
4680
     *    // => [1,2,3,4]
4681
     */
4682
    unique: function() {
4683
      var vals = [],
4684
          max = arr.length,
4685
          idx = 0;
4686
4687
      while (idx < max) {
4688
        if (!wysihtml5.lang.array(vals).contains(arr[idx])) {
4689
          vals.push(arr[idx]);
4690
        }
4691
        idx++;
4692
      }
4693
      return vals;
4694
    }
4695
4696
  };
4697
};
4698
;wysihtml5.lang.Dispatcher = Base.extend(
4699
  /** @scope wysihtml5.lang.Dialog.prototype */ {
4700
  on: function(eventName, handler) {
4701
    this.events = this.events || {};
4702
    this.events[eventName] = this.events[eventName] || [];
4703
    this.events[eventName].push(handler);
4704
    return this;
4705
  },
4706
4707
  off: function(eventName, handler) {
4708
    this.events = this.events || {};
4709
    var i = 0,
4710
        handlers,
4711
        newHandlers;
4712
    if (eventName) {
4713
      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...
4714
      newHandlers = [];
4715
      for (; i<handlers.length; i++) {
4716
        if (handlers[i] !== handler && handler) {
4717
          newHandlers.push(handlers[i]);
4718
        }
4719
      }
4720
      this.events[eventName] = newHandlers;
4721
    } else {
4722
      // Clean up all events
4723
      this.events = {};
4724
    }
4725
    return this;
4726
  },
4727
4728
  fire: function(eventName, payload) {
4729
    this.events = this.events || {};
4730
    var handlers = this.events[eventName] || [],
4731
        i        = 0;
4732
    for (; i<handlers.length; i++) {
4733
      handlers[i].call(this, payload);
4734
    }
4735
    return this;
4736
  },
4737
4738
  // deprecated, use .on()
4739
  observe: function() {
4740
    return this.on.apply(this, arguments);
4741
  },
4742
4743
  // deprecated, use .off()
4744
  stopObserving: function() {
4745
    return this.off.apply(this, arguments);
4746
  }
4747
});
4748
;wysihtml5.lang.object = function(obj) {
4749
  return {
4750
    /**
4751
     * @example
4752
     *    wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
4753
     *    // => { foo: 1, bar: 2, baz: 3 }
4754
     */
4755
    merge: function(otherObj) {
4756
      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...
4757
        obj[i] = otherObj[i];
4758
      }
4759
      return this;
4760
    },
4761
4762
    get: function() {
4763
      return obj;
4764
    },
4765
4766
    /**
4767
     * @example
4768
     *    wysihtml5.lang.object({ foo: 1 }).clone();
4769
     *    // => { foo: 1 }
4770
     */
4771
    clone: function() {
4772
      var newObj = {},
4773
          i;
4774
      for (i in obj) {
4775
        newObj[i] = obj[i];
4776
      }
4777
      return newObj;
4778
    },
4779
4780
    /**
4781
     * @example
4782
     *    wysihtml5.lang.object([]).isArray();
4783
     *    // => true
4784
     */
4785
    isArray: function() {
4786
      return Object.prototype.toString.call(obj) === "[object Array]";
4787
    }
4788
  };
4789
};
4790
;(function() {
4791
  var WHITE_SPACE_START = /^\s+/,
4792
      WHITE_SPACE_END   = /\s+$/,
4793
      ENTITY_REG_EXP    = /[&<>"]/g,
4794
      ENTITY_MAP = {
4795
        '&': '&amp;',
4796
        '<': '&lt;',
4797
        '>': '&gt;',
4798
        '"': "&quot;"
4799
      };
4800
  wysihtml5.lang.string = function(str) {
4801
    str = String(str);
4802
    return {
4803
      /**
4804
       * @example
4805
       *    wysihtml5.lang.string("   foo   ").trim();
4806
       *    // => "foo"
4807
       */
4808
      trim: function() {
4809
        return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
4810
      },
4811
4812
      /**
4813
       * @example
4814
       *    wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
4815
       *    // => "Hello Christopher"
4816
       */
4817
      interpolate: function(vars) {
4818
        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...
4819
          str = this.replace("#{" + i + "}").by(vars[i]);
4820
        }
4821
        return str;
4822
      },
4823
4824
      /**
4825
       * @example
4826
       *    wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans");
4827
       *    // => "Hello Hans"
4828
       */
4829
      replace: function(search) {
4830
        return {
4831
          by: function(replace) {
4832
            return str.split(search).join(replace);
4833
          }
4834
        };
4835
      },
4836
4837
      /**
4838
       * @example
4839
       *    wysihtml5.lang.string("hello<br>").escapeHTML();
4840
       *    // => "hello&lt;br&gt;"
4841
       */
4842
      escapeHTML: function() {
4843
        return str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
4844
      }
4845
    };
4846
  };
4847
})();
4848
;/**
4849
 * Find urls in descendant text nodes of an element and auto-links them
4850
 * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
4851
 *
4852
 * @param {Element} element Container element in which to search for urls
4853
 *
4854
 * @example
4855
 *    <div id="text-container">Please click here: www.google.com</div>
4856
 *    <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
4857
 */
4858
(function(wysihtml5) {
4859
  var /**
4860
       * Don't auto-link urls that are contained in the following elements:
4861
       */
4862
      IGNORE_URLS_IN        = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
4863
      /**
4864
       * revision 1:
4865
       *    /(\S+\.{1}[^\s\,\.\!]+)/g
4866
       *
4867
       * revision 2:
4868
       *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
4869
       *
4870
       * put this in the beginning if you don't wan't to match within a word
4871
       *    (^|[\>\(\{\[\s\>])
4872
       */
4873
      URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
4874
      TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
4875
      MAX_DISPLAY_LENGTH    = 100,
4876
      BRACKETS              = { ")": "(", "]": "[", "}": "{" };
4877
4878
  function autoLink(element, ignoreInClasses) {
4879
    if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
4880
      return element;
4881
    }
4882
4883
    if (element === element.ownerDocument.documentElement) {
4884
      element = element.ownerDocument.body;
4885
    }
4886
4887
    return _parseNode(element, ignoreInClasses);
4888
  }
4889
4890
  /**
4891
   * This is basically a rebuild of
4892
   * the rails auto_link_urls text helper
4893
   */
4894
  function _convertUrlsToLinks(str) {
4895
    return str.replace(URL_REG_EXP, function(match, url) {
4896
      var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
4897
          opening     = BRACKETS[punctuation];
4898
      url = url.replace(TRAILING_CHAR_REG_EXP, "");
4899
4900
      if (url.split(opening).length > url.split(punctuation).length) {
4901
        url = url + punctuation;
4902
        punctuation = "";
4903
      }
4904
      var realUrl    = url,
4905
          displayUrl = url;
4906
      if (url.length > MAX_DISPLAY_LENGTH) {
4907
        displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
4908
      }
4909
      // Add http prefix if necessary
4910
      if (realUrl.substr(0, 4) === "www.") {
4911
        realUrl = "http://" + realUrl;
4912
      }
4913
4914
      return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
4915
    });
4916
  }
4917
4918
  /**
4919
   * Creates or (if already cached) returns a temp element
4920
   * for the given document object
4921
   */
4922
  function _getTempElement(context) {
4923
    var tempElement = context._wysihtml5_tempElement;
4924
    if (!tempElement) {
4925
      tempElement = context._wysihtml5_tempElement = context.createElement("div");
4926
    }
4927
    return tempElement;
4928
  }
4929
4930
  /**
4931
   * Replaces the original text nodes with the newly auto-linked dom tree
4932
   */
4933
  function _wrapMatchesInNode(textNode) {
4934
    var parentNode  = textNode.parentNode,
4935
        nodeValue   = wysihtml5.lang.string(textNode.data).escapeHTML(),
4936
        tempElement = _getTempElement(parentNode.ownerDocument);
4937
4938
    // We need to insert an empty/temporary <span /> to fix IE quirks
4939
    // Elsewise IE would strip white space in the beginning
4940
    tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
4941
    tempElement.removeChild(tempElement.firstChild);
4942
4943
    while (tempElement.firstChild) {
4944
      // inserts tempElement.firstChild before textNode
4945
      parentNode.insertBefore(tempElement.firstChild, textNode);
4946
    }
4947
    parentNode.removeChild(textNode);
4948
  }
4949
4950
  function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
4951
    var nodeName;
4952
    while (node.parentNode) {
4953
      node = node.parentNode;
4954
      nodeName = node.nodeName;
4955
      if (node.className && wysihtml5.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
4956
        return true;
4957
      }
4958
      if (IGNORE_URLS_IN.contains(nodeName)) {
4959
        return true;
4960
      } else if (nodeName === "body") {
4961
        return false;
4962
      }
4963
    }
4964
    return false;
4965
  }
4966
4967
  function _parseNode(element, ignoreInClasses) {
4968
    if (IGNORE_URLS_IN.contains(element.nodeName)) {
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.className && wysihtml5.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
4973
      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...
4974
    }
4975
4976
    if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) {
4977
      _wrapMatchesInNode(element);
4978
      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...
4979
    }
4980
4981
    var childNodes        = wysihtml5.lang.array(element.childNodes).get(),
4982
        childNodesLength  = childNodes.length,
4983
        i                 = 0;
4984
4985
    for (; i<childNodesLength; i++) {
4986
      _parseNode(childNodes[i], ignoreInClasses);
4987
    }
4988
4989
    return element;
4990
  }
4991
4992
  wysihtml5.dom.autoLink = autoLink;
4993
4994
  // Reveal url reg exp to the outside
4995
  wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
4996
})(wysihtml5);
4997
;(function(wysihtml5) {
4998
  var api = wysihtml5.dom;
4999
5000
  api.addClass = function(element, className) {
5001
    var classList = element.classList;
5002
    if (classList) {
5003
      return classList.add(className);
5004
    }
5005
    if (api.hasClass(element, className)) {
5006
      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...
5007
    }
5008
    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...
5009
  };
5010
5011
  api.removeClass = function(element, className) {
5012
    var classList = element.classList;
5013
    if (classList) {
5014
      return classList.remove(className);
5015
    }
5016
5017
    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...
5018
  };
5019
5020
  api.hasClass = function(element, className) {
5021
    var classList = element.classList;
5022
    if (classList) {
5023
      return classList.contains(className);
5024
    }
5025
5026
    var elementClassName = element.className;
5027
    return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
5028
  };
5029
})(wysihtml5);
5030
;wysihtml5.dom.contains = (function() {
5031
  var documentElement = document.documentElement;
5032
  if (documentElement.contains) {
5033
    return function(container, element) {
5034
      if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5035
        element = element.parentNode;
5036
      }
5037
      return container !== element && container.contains(element);
5038
    };
5039
  } 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...
5040
    return function(container, element) {
5041
      // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
5042
      return !!(container.compareDocumentPosition(element) & 16);
5043
    };
5044
  }
5045
})();
5046
;/**
5047
 * Converts an HTML fragment/element into a unordered/ordered list
5048
 *
5049
 * @param {Element} element The element which should be turned into a list
5050
 * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
5051
 * @return {Element} The created list
5052
 *
5053
 * @example
5054
 *    <!-- Assume the following dom: -->
5055
 *    <span id="pseudo-list">
5056
 *      eminem<br>
5057
 *      dr. dre
5058
 *      <div>50 Cent</div>
5059
 *    </span>
5060
 *
5061
 *    <script>
5062
 *      wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul");
5063
 *    </script>
5064
 *
5065
 *    <!-- Will result in: -->
5066
 *    <ul>
5067
 *      <li>eminem</li>
5068
 *      <li>dr. dre</li>
5069
 *      <li>50 Cent</li>
5070
 *    </ul>
5071
 */
5072
wysihtml5.dom.convertToList = (function() {
5073
  function _createListItem(doc, list) {
5074
    var listItem = doc.createElement("li");
5075
    list.appendChild(listItem);
5076
    return listItem;
5077
  }
5078
5079
  function _createList(doc, type) {
5080
    return doc.createElement(type);
5081
  }
5082
5083
  function convertToList(element, listType, uneditableClass) {
5084
    if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
5085
      // Already a list
5086
      return element;
5087
    }
5088
5089
    var doc               = element.ownerDocument,
5090
        list              = _createList(doc, listType),
5091
        lineBreaks        = element.querySelectorAll("br"),
5092
        lineBreaksLength  = lineBreaks.length,
5093
        childNodes,
5094
        childNodesLength,
5095
        childNode,
5096
        lineBreak,
5097
        parentNode,
5098
        isBlockElement,
5099
        isLineBreak,
5100
        currentListItem,
5101
        i;
5102
5103
    // First find <br> at the end of inline elements and move them behind them
5104
    for (i=0; i<lineBreaksLength; i++) {
5105
      lineBreak = lineBreaks[i];
5106
      while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
5107
        if (wysihtml5.dom.getStyle("display").from(parentNode) === "block") {
5108
          parentNode.removeChild(lineBreak);
5109
          break;
5110
        }
5111
        wysihtml5.dom.insert(lineBreak).after(lineBreak.parentNode);
5112
      }
5113
    }
5114
5115
    childNodes        = wysihtml5.lang.array(element.childNodes).get();
5116
    childNodesLength  = childNodes.length;
5117
5118
    for (i=0; i<childNodesLength; i++) {
5119
      currentListItem   = currentListItem || _createListItem(doc, list);
5120
      childNode         = childNodes[i];
5121
      isBlockElement    = wysihtml5.dom.getStyle("display").from(childNode) === "block";
5122
      isLineBreak       = childNode.nodeName === "BR";
5123
5124
      // consider uneditable as an inline element
5125
      if (isBlockElement && (!uneditableClass || !wysihtml5.dom.hasClass(childNode, uneditableClass))) {
5126
        // Append blockElement to current <li> if empty, otherwise create a new one
5127
        currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
5128
        currentListItem.appendChild(childNode);
5129
        currentListItem = null;
5130
        continue;
5131
      }
5132
5133
      if (isLineBreak) {
5134
        // Only create a new list item in the next iteration when the current one has already content
5135
        currentListItem = currentListItem.firstChild ? null : currentListItem;
5136
        continue;
5137
      }
5138
5139
      currentListItem.appendChild(childNode);
5140
    }
5141
5142
    if (childNodes.length === 0) {
5143
      _createListItem(doc, list);
5144
    }
5145
5146
    element.parentNode.replaceChild(list, element);
5147
    return list;
5148
  }
5149
5150
  return convertToList;
5151
})();
5152
;/**
5153
 * Copy a set of attributes from one element to another
5154
 *
5155
 * @param {Array} attributesToCopy List of attributes which should be copied
5156
 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
5157
 *    copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
5158
 *    with the element where to copy the attributes to (see example)
5159
 *
5160
 * @example
5161
 *    var textarea    = document.querySelector("textarea"),
5162
 *        div         = document.querySelector("div[contenteditable=true]"),
5163
 *        anotherDiv  = document.querySelector("div.preview");
5164
 *    wysihtml5.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
5165
 *
5166
 */
5167
wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5168
  return {
5169
    from: function(elementToCopyFrom) {
5170
      return {
5171
        to: function(elementToCopyTo) {
5172
          var attribute,
5173
              i         = 0,
5174
              length    = attributesToCopy.length;
5175
          for (; i<length; i++) {
5176
            attribute = attributesToCopy[i];
5177
            if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
5178
              elementToCopyTo[attribute] = elementToCopyFrom[attribute];
5179
            }
5180
          }
5181
          return { andTo: arguments.callee };
5182
        }
5183
      };
5184
    }
5185
  };
5186
};
5187
;/**
5188
 * Copy a set of styles from one element to another
5189
 * Please note that this only works properly across browsers when the element from which to copy the styles
5190
 * is in the dom
5191
 *
5192
 * Interesting article on how to copy styles
5193
 *
5194
 * @param {Array} stylesToCopy List of styles which should be copied
5195
 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
5196
 *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked
5197
 *    with the element where to copy the styles to (see example)
5198
 *
5199
 * @example
5200
 *    var textarea    = document.querySelector("textarea"),
5201
 *        div         = document.querySelector("div[contenteditable=true]"),
5202
 *        anotherDiv  = document.querySelector("div.preview");
5203
 *    wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
5204
 *
5205
 */
5206
(function(dom) {
5207
5208
  /**
5209
   * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
5210
   * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
5211
   * its computed css width will be 198px
5212
   *
5213
   * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
5214
   */
5215
  var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
5216
5217
  var shouldIgnoreBoxSizingBorderBox = function(element) {
5218
    if (hasBoxSizingBorderBox(element)) {
5219
       return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
5220
    }
5221
    return false;
5222
  };
5223
5224
  var hasBoxSizingBorderBox = function(element) {
5225
    var i       = 0,
5226
        length  = BOX_SIZING_PROPERTIES.length;
5227
    for (; i<length; i++) {
5228
      if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
5229
        return BOX_SIZING_PROPERTIES[i];
5230
      }
5231
    }
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...
5232
  };
5233
5234
  dom.copyStyles = function(stylesToCopy) {
5235
    return {
5236
      from: function(element) {
5237
        if (shouldIgnoreBoxSizingBorderBox(element)) {
5238
          stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
5239
        }
5240
5241
        var cssText = "",
5242
            length  = stylesToCopy.length,
5243
            i       = 0,
5244
            property;
5245
        for (; i<length; i++) {
5246
          property = stylesToCopy[i];
5247
          cssText += property + ":" + dom.getStyle(property).from(element) + ";";
5248
        }
5249
5250
        return {
5251
          to: function(element) {
5252
            dom.setStyles(cssText).on(element);
5253
            return { andTo: arguments.callee };
5254
          }
5255
        };
5256
      }
5257
    };
5258
  };
5259
})(wysihtml5.dom);
5260
;/**
5261
 * Event Delegation
5262
 *
5263
 * @example
5264
 *    wysihtml5.dom.delegate(document.body, "a", "click", function() {
5265
 *      // foo
5266
 *    });
5267
 */
5268
(function(wysihtml5) {
5269
5270
  wysihtml5.dom.delegate = function(container, selector, eventName, handler) {
5271
    return wysihtml5.dom.observe(container, eventName, function(event) {
5272
      var target    = event.target,
5273
          match     = wysihtml5.lang.array(container.querySelectorAll(selector));
5274
5275
      while (target && target !== container) {
5276
        if (match.contains(target)) {
5277
          handler.call(target, event);
5278
          break;
5279
        }
5280
        target = target.parentNode;
5281
      }
5282
    });
5283
  };
5284
5285
})(wysihtml5);
5286
;// TODO: Refactor dom tree traversing here
5287
(function(wysihtml5) {
5288
  wysihtml5.dom.domNode = function(node) {
5289
    var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
5290
5291
    var _isBlankText = function(node) {
5292
      return node.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/g).test(node.data);
5293
    };
5294
5295
    return {
5296
5297
      // var node = wysihtml5.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
5298
      prev: function(options) {
5299
        var prevNode = node.previousSibling,
5300
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5301
        
5302
        if (!prevNode) {
5303
          return null;
5304
        }
5305
5306
        if (
5307
          (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
5308
          (options && options.ignoreBlankTexts && _isBlankText(prevNode)) // Blank text nodes bypassed if set
5309
        ) {
5310
          return wysihtml5.dom.domNode(prevNode).prev(options);
5311
        }
5312
        
5313
        return prevNode;
5314
      },
5315
5316
      // var node = wysihtml5.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
5317
      next: function(options) {
5318
        var nextNode = node.nextSibling,
5319
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5320
        
5321
        if (!nextNode) {
5322
          return null;
5323
        }
5324
5325
        if (
5326
          (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
5327
          (options && options.ignoreBlankTexts && _isBlankText(nextNode)) // blank text nodes bypassed if set
5328
        ) {
5329
          return wysihtml5.dom.domNode(nextNode).next(options);
5330
        }
5331
        
5332
        return nextNode;
5333
      }
5334
5335
5336
5337
    };
5338
  };
5339
})(wysihtml5);;/**
5340
 * Returns the given html wrapped in a div element
5341
 *
5342
 * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
5343
 * when inserted via innerHTML
5344
 *
5345
 * @param {String} html The html which should be wrapped in a dom element
5346
 * @param {Obejct} [context] Document object of the context the html belongs to
5347
 *
5348
 * @example
5349
 *    wysihtml5.dom.getAsDom("<article>foo</article>");
5350
 */
5351
wysihtml5.dom.getAsDom = (function() {
5352
5353
  var _innerHTMLShiv = function(html, context) {
5354
    var tempElement = context.createElement("div");
5355
    tempElement.style.display = "none";
5356
    context.body.appendChild(tempElement);
5357
    // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
5358
    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...
5359
    context.body.removeChild(tempElement);
5360
    return tempElement;
5361
  };
5362
5363
  /**
5364
   * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
5365
   */
5366
  var _ensureHTML5Compatibility = function(context) {
5367
    if (context._wysihtml5_supportsHTML5Tags) {
5368
      return;
5369
    }
5370
    for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
5371
      context.createElement(HTML5_ELEMENTS[i]);
5372
    }
5373
    context._wysihtml5_supportsHTML5Tags = true;
5374
  };
5375
5376
5377
  /**
5378
   * List of html5 tags
5379
   * taken from http://simon.html5.org/html5-elements
5380
   */
5381
  var HTML5_ELEMENTS = [
5382
    "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
5383
    "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
5384
    "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
5385
  ];
5386
5387
  return function(html, context) {
5388
    context = context || document;
5389
    var tempElement;
5390
    if (typeof(html) === "object" && html.nodeType) {
5391
      tempElement = context.createElement("div");
5392
      tempElement.appendChild(html);
5393
    } else if (wysihtml5.browser.supportsHTML5Tags(context)) {
5394
      tempElement = context.createElement("div");
5395
      tempElement.innerHTML = html;
5396
    } else {
5397
      _ensureHTML5Compatibility(context);
5398
      tempElement = _innerHTMLShiv(html, context);
5399
    }
5400
    return tempElement;
5401
  };
5402
})();
5403
;/**
5404
 * Walks the dom tree from the given node up until it finds a match
5405
 * Designed for optimal performance.
5406
 *
5407
 * @param {Element} node The from which to check the parent nodes
5408
 * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
5409
 * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
5410
 * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
5411
 * @example
5412
 *    var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] });
5413
 *    // ... or ...
5414
 *    var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" });
5415
 *    // ... or ...
5416
 *    var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g });
5417
 */
5418
wysihtml5.dom.getParentElement = (function() {
5419
5420
  function _isSameNodeName(nodeName, desiredNodeNames) {
5421
    if (!desiredNodeNames || !desiredNodeNames.length) {
5422
      return true;
5423
    }
5424
5425
    if (typeof(desiredNodeNames) === "string") {
5426
      return nodeName === desiredNodeNames;
5427
    } 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...
5428
      return wysihtml5.lang.array(desiredNodeNames).contains(nodeName);
5429
    }
5430
  }
5431
5432
  function _isElement(node) {
5433
    return node.nodeType === wysihtml5.ELEMENT_NODE;
5434
  }
5435
5436
  function _hasClassName(element, className, classRegExp) {
5437
    var classNames = (element.className || "").match(classRegExp) || [];
5438
    if (!className) {
5439
      return !!classNames.length;
5440
    }
5441
    return classNames[classNames.length - 1] === className;
5442
  }
5443
5444
  function _hasStyle(element, cssStyle, styleRegExp) {
5445
    var styles = (element.getAttribute('style') || "").match(styleRegExp) || [];
5446
    if (!cssStyle) {
5447
      return !!styles.length;
5448
    }
5449
    return styles[styles.length - 1] === cssStyle;
5450
  }
5451
5452
  return function(node, matchingSet, levels, container) {
5453
    var findByStyle = (matchingSet.cssStyle || matchingSet.styleRegExp),
5454
        findByClass = (matchingSet.className || matchingSet.classRegExp);
5455
5456
    levels = levels || 50; // Go max 50 nodes upwards from current node
5457
5458
    while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
5459
      if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
5460
          (!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
5461
          (!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
5462
      ) {
5463
        return node;
5464
      }
5465
      node = node.parentNode;
5466
    }
5467
    return null;
5468
  };
5469
})();
5470
;/**
5471
 * Get element's style for a specific css property
5472
 *
5473
 * @param {Element} element The element on which to retrieve the style
5474
 * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
5475
 *
5476
 * @example
5477
 *    wysihtml5.dom.getStyle("display").from(document.body);
5478
 *    // => "block"
5479
 */
5480
wysihtml5.dom.getStyle = (function() {
5481
  var stylePropertyMapping = {
5482
        "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
5483
      },
5484
      REG_EXP_CAMELIZE = /\-[a-z]/g;
5485
5486
  function camelize(str) {
5487
    return str.replace(REG_EXP_CAMELIZE, function(match) {
5488
      return match.charAt(1).toUpperCase();
5489
    });
5490
  }
5491
5492
  return function(property) {
5493
    return {
5494
      from: function(element) {
5495
        if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5496
          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...
5497
        }
5498
5499
        var doc               = element.ownerDocument,
5500
            camelizedProperty = stylePropertyMapping[property] || camelize(property),
5501
            style             = element.style,
5502
            currentStyle      = element.currentStyle,
5503
            styleValue        = style[camelizedProperty];
5504
        if (styleValue) {
5505
          return styleValue;
5506
        }
5507
5508
        // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
5509
        // window.getComputedStyle, since it returns css property values in their original unit:
5510
        // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
5511
        // gives you the original "50%".
5512
        // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
5513
        if (currentStyle) {
5514
          try {
5515
            return currentStyle[camelizedProperty];
5516
          } 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...
5517
            //ie will occasionally fail for unknown reasons. swallowing exception
5518
          }
5519
        }
5520
5521
        var win                 = doc.defaultView || doc.parentWindow,
5522
            needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
5523
            originalOverflow,
5524
            returnValue;
5525
5526
        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...
5527
          // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
5528
          // therfore we remove and restore the scrollbar and calculate the value in between
5529
          if (needsOverflowReset) {
5530
            originalOverflow = style.overflow;
5531
            style.overflow = "hidden";
5532
          }
5533
          returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
5534
          if (needsOverflowReset) {
5535
            style.overflow = originalOverflow || "";
5536
          }
5537
          return returnValue;
5538
        }
5539
      }
5540
    };
5541
  };
5542
})();
5543
;wysihtml5.dom.getTextNodes = function(node, ingoreEmpty){
5544
  var all = [];
5545
  for (node=node.firstChild;node;node=node.nextSibling){
5546
    if (node.nodeType == 3) {
5547
      if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
5548
        all.push(node);
5549
      }
5550
    } else {
5551
      all = all.concat(wysihtml5.dom.getTextNodes(node, ingoreEmpty));
5552
    }
5553
  }
5554
  return all;
5555
};;/**
5556
 * High performant way to check whether an element with a specific tag name is in the given document
5557
 * Optimized for being heavily executed
5558
 * Unleashes the power of live node lists
5559
 *
5560
 * @param {Object} doc The document object of the context where to check
5561
 * @param {String} tagName Upper cased tag name
5562
 * @example
5563
 *    wysihtml5.dom.hasElementWithTagName(document, "IMG");
5564
 */
5565
wysihtml5.dom.hasElementWithTagName = (function() {
5566
  var LIVE_CACHE          = {},
5567
      DOCUMENT_IDENTIFIER = 1;
5568
5569
  function _getDocumentIdentifier(doc) {
5570
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5571
  }
5572
5573
  return function(doc, tagName) {
5574
    var key         = _getDocumentIdentifier(doc) + ":" + tagName,
5575
        cacheEntry  = LIVE_CACHE[key];
5576
    if (!cacheEntry) {
5577
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
5578
    }
5579
5580
    return cacheEntry.length > 0;
5581
  };
5582
})();
5583
;/**
5584
 * High performant way to check whether an element with a specific class name is in the given document
5585
 * Optimized for being heavily executed
5586
 * Unleashes the power of live node lists
5587
 *
5588
 * @param {Object} doc The document object of the context where to check
5589
 * @param {String} tagName Upper cased tag name
5590
 * @example
5591
 *    wysihtml5.dom.hasElementWithClassName(document, "foobar");
5592
 */
5593
(function(wysihtml5) {
5594
  var LIVE_CACHE          = {},
5595
      DOCUMENT_IDENTIFIER = 1;
5596
5597
  function _getDocumentIdentifier(doc) {
5598
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5599
  }
5600
5601
  wysihtml5.dom.hasElementWithClassName = function(doc, className) {
5602
    // getElementsByClassName is not supported by IE<9
5603
    // but is sometimes mocked via library code (which then doesn't return live node lists)
5604
    if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) {
5605
      return !!doc.querySelector("." + className);
5606
    }
5607
5608
    var key         = _getDocumentIdentifier(doc) + ":" + className,
5609
        cacheEntry  = LIVE_CACHE[key];
5610
    if (!cacheEntry) {
5611
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
5612
    }
5613
5614
    return cacheEntry.length > 0;
5615
  };
5616
})(wysihtml5);
5617
;wysihtml5.dom.insert = function(elementToInsert) {
5618
  return {
5619
    after: function(element) {
5620
      element.parentNode.insertBefore(elementToInsert, element.nextSibling);
5621
    },
5622
5623
    before: function(element) {
5624
      element.parentNode.insertBefore(elementToInsert, element);
5625
    },
5626
5627
    into: function(element) {
5628
      element.appendChild(elementToInsert);
5629
    }
5630
  };
5631
};
5632
;wysihtml5.dom.insertCSS = function(rules) {
5633
  rules = rules.join("\n");
5634
5635
  return {
5636
    into: function(doc) {
5637
      var styleElement = doc.createElement("style");
5638
      styleElement.type = "text/css";
5639
5640
      if (styleElement.styleSheet) {
5641
        styleElement.styleSheet.cssText = rules;
5642
      } else {
5643
        styleElement.appendChild(doc.createTextNode(rules));
5644
      }
5645
5646
      var link = doc.querySelector("head link");
5647
      if (link) {
5648
        link.parentNode.insertBefore(styleElement, link);
5649
        return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
5650
      } 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...
5651
        var head = doc.querySelector("head");
5652
        if (head) {
5653
          head.appendChild(styleElement);
5654
        }
5655
      }
5656
    }
5657
  };
5658
};
5659
;// TODO: Refactor dom tree traversing here
5660
(function(wysihtml5) {
5661
  wysihtml5.dom.lineBreaks = function(node) {
5662
5663
    function _isLineBreak(n) {
5664
      return n.nodeName === "BR";
5665
    }
5666
5667
    /**
5668
     * Checks whether the elment causes a visual line break
5669
     * (<br> or block elements)
5670
     */
5671
    function _isLineBreakOrBlockElement(element) {
5672
      if (_isLineBreak(element)) {
5673
        return true;
5674
      }
5675
5676
      if (wysihtml5.dom.getStyle("display").from(element) === "block") {
5677
        return true;
5678
      }
5679
5680
      return false;
5681
    }
5682
5683
    return {
5684
5685
      /* wysihtml5.dom.lineBreaks(element).add();
5686
       *
5687
       * Adds line breaks before and after the given node if the previous and next siblings
5688
       * aren't already causing a visual line break (block element or <br>)
5689
       */
5690
      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...
5691
        var doc             = node.ownerDocument,
5692
          nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5693
          previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5694
5695
        if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
5696
          wysihtml5.dom.insert(doc.createElement("br")).after(node);
5697
        }
5698
        if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
5699
          wysihtml5.dom.insert(doc.createElement("br")).before(node);
5700
        }
5701
      },
5702
5703
      /* wysihtml5.dom.lineBreaks(element).remove();
5704
       *
5705
       * Removes line breaks before and after the given node
5706
       */
5707
      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...
5708
        var nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5709
            previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5710
5711
        if (nextSibling && _isLineBreak(nextSibling)) {
5712
          nextSibling.parentNode.removeChild(nextSibling);
5713
        }
5714
        if (previousSibling && _isLineBreak(previousSibling)) {
5715
          previousSibling.parentNode.removeChild(previousSibling);
5716
        }
5717
      }
5718
    };
5719
  };
5720
})(wysihtml5);;/**
5721
 * Method to set dom events
5722
 *
5723
 * @example
5724
 *    wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
5725
 */
5726
wysihtml5.dom.observe = function(element, eventNames, handler) {
5727
  eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
5728
5729
  var handlerWrapper,
5730
      eventName,
5731
      i       = 0,
5732
      length  = eventNames.length;
5733
5734
  for (; i<length; i++) {
5735
    eventName = eventNames[i];
5736
    if (element.addEventListener) {
5737
      element.addEventListener(eventName, handler, false);
5738
    } else {
5739
      handlerWrapper = function(event) {
5740
        if (!("target" in event)) {
5741
          event.target = event.srcElement;
5742
        }
5743
        event.preventDefault = event.preventDefault || function() {
5744
          this.returnValue = false;
5745
        };
5746
        event.stopPropagation = event.stopPropagation || function() {
5747
          this.cancelBubble = true;
5748
        };
5749
        handler.call(element, event);
5750
      };
5751
      element.attachEvent("on" + eventName, handlerWrapper);
5752
    }
5753
  }
5754
5755
  return {
5756
    stop: function() {
5757
      var eventName,
5758
          i       = 0,
5759
          length  = eventNames.length;
5760
      for (; i<length; i++) {
5761
        eventName = eventNames[i];
5762
        if (element.removeEventListener) {
5763
          element.removeEventListener(eventName, handler, false);
5764
        } else {
5765
          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...
5766
        }
5767
      }
5768
    }
5769
  };
5770
};
5771
;/**
5772
 * HTML Sanitizer
5773
 * Rewrites the HTML based on given rules
5774
 *
5775
 * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
5776
 * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
5777
 *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
5778
 *    desired substitution.
5779
 * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
5780
 *
5781
 * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
5782
 *
5783
 * @example
5784
 *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
5785
 *    wysihtml5.dom.parse(userHTML, {
5786
 *      tags {
5787
 *        p:      "div",      // Rename p tags to div tags
5788
 *        font:   "span"      // Rename font tags to span tags
5789
 *        div:    true,       // Keep them, also possible (same result when passing: "div" or true)
5790
 *        script: undefined   // Remove script elements
5791
 *      }
5792
 *    });
5793
 *    // => <div><div><span>foo bar</span></div></div>
5794
 *
5795
 *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
5796
 *    wysihtml5.dom.parse(userHTML);
5797
 *    // => '<span><span><span><span>I'm a table!</span></span></span></span>'
5798
 *
5799
 *    var userHTML = '<div>foobar<br>foobar</div>';
5800
 *    wysihtml5.dom.parse(userHTML, {
5801
 *      tags: {
5802
 *        div: undefined,
5803
 *        br:  true
5804
 *      }
5805
 *    });
5806
 *    // => ''
5807
 *
5808
 *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
5809
 *    wysihtml5.dom.parse(userHTML, {
5810
 *      classes: {
5811
 *        red:    1,
5812
 *        green:  1
5813
 *      },
5814
 *      tags: {
5815
 *        div: {
5816
 *          rename_tag:     "p"
5817
 *        }
5818
 *      }
5819
 *    });
5820
 *    // => '<p class="red">foo</p><p>bar</p>'
5821
 */
5822
5823
wysihtml5.dom.parse = (function() {
5824
5825
  /**
5826
   * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
5827
   * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
5828
   * node isn't closed
5829
   *
5830
   * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
5831
   */
5832
  var NODE_TYPE_MAPPING = {
5833
        "1": _handleElement,
5834
        "3": _handleText,
5835
        "8": _handleComment
5836
      },
5837
      // Rename unknown tags to this
5838
      DEFAULT_NODE_NAME   = "span",
5839
      WHITE_SPACE_REG_EXP = /\s+/,
5840
      defaultRules        = { tags: {}, classes: {} },
5841
      currentRules        = {},
5842
      uneditableClass     = false;
5843
5844
  /**
5845
   * Iterates over all childs of the element, recreates them, appends them into a document fragment
5846
   * which later replaces the entire body content
5847
   */
5848
   function parse(elementOrHtml, config) {
5849
    wysihtml5.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
5850
5851
    var context       = config.context || elementOrHtml.ownerDocument || document,
5852
        fragment      = context.createDocumentFragment(),
5853
        isString      = typeof(elementOrHtml) === "string",
5854
        clearInternals = false,
5855
        element,
5856
        newNode,
5857
        firstChild;
5858
5859
    if (config.clearInternals === true) {
5860
      clearInternals = true;
5861
    }
5862
5863
    if (config.uneditableClass) {
5864
      uneditableClass = config.uneditableClass;
5865
    }
5866
5867
    if (isString) {
5868
      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
5869
    } else {
5870
      element = elementOrHtml;
5871
    }
5872
5873
    while (element.firstChild) {
5874
      firstChild = element.firstChild;
5875
      newNode = _convert(firstChild, config.cleanUp, clearInternals);
5876
      if (newNode) {
5877
        fragment.appendChild(newNode);
5878
      }
5879
      if (firstChild !== newNode) {
5880
        element.removeChild(firstChild);
5881
      }
5882
    }
5883
5884
    // Clear element contents
5885
    element.innerHTML = "";
5886
5887
    // Insert new DOM tree
5888
    element.appendChild(fragment);
5889
5890
    return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
5891
  }
5892
5893
  function _convert(oldNode, cleanUp, clearInternals) {
5894
    var oldNodeType     = oldNode.nodeType,
5895
        oldChilds       = oldNode.childNodes,
5896
        oldChildsLength = oldChilds.length,
5897
        method          = NODE_TYPE_MAPPING[oldNodeType],
5898
        i               = 0,
5899
        fragment,
5900
        newNode,
5901
        newChild;
5902
5903
    // Passes directly elemets with uneditable class
5904
    if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
5905
        return oldNode;
5906
    }
5907
5908
    newNode = method && method(oldNode, clearInternals);
5909
5910
    // Remove or unwrap node in case of return value null or false
5911
    if (!newNode) {
5912
        if (newNode === false) {
5913
            // false defines that tag should be removed but contents should remain (unwrap)
5914
            fragment = oldNode.ownerDocument.createDocumentFragment();
5915
5916
            for (i = oldChildsLength; i--;) {
5917
              if (oldChilds[i]) {
5918
                newChild = _convert(oldChilds[i], cleanUp, clearInternals);
5919
                if (newChild) {
5920
                  if (oldChilds[i] === newChild) {
5921
                    i--;
5922
                  }
5923
                  fragment.insertBefore(newChild, fragment.firstChild);
5924
                }
5925
              }
5926
            }
5927
5928
            // TODO: try to minimize surplus spaces
5929
            if (wysihtml5.lang.array([
5930
                "div", "pre", "p",
5931
                "table", "td", "th",
5932
                "ul", "ol", "li",
5933
                "dd", "dl",
5934
                "footer", "header", "section",
5935
                "h1", "h2", "h3", "h4", "h5", "h6"
5936
            ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
5937
                // add space at first when unwraping non-textflow elements
5938
                if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
5939
                  fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
5940
                }
5941
            }
5942
5943
            if (fragment.normalize) {
5944
              fragment.normalize();
5945
            }
5946
            return fragment;
5947
        } 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...
5948
          // Remove
5949
          return null;
5950
        }
5951
    }
5952
5953
    // Converts all childnodes
5954
    for (i=0; i<oldChildsLength; i++) {
5955
      if (oldChilds[i]) {
5956
        newChild = _convert(oldChilds[i], cleanUp, clearInternals);
5957
        if (newChild) {
5958
          if (oldChilds[i] === newChild) {
5959
            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...
5960
          }
5961
          newNode.appendChild(newChild);
5962
        }
5963
      }
5964
    }
5965
5966
    // Cleanup senseless <span> elements
5967
    if (cleanUp &&
5968
        newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
5969
        (!newNode.childNodes.length ||
5970
         ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml5-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
5971
         !newNode.attributes.length)
5972
        ) {
5973
      fragment = newNode.ownerDocument.createDocumentFragment();
5974
      while (newNode.firstChild) {
5975
        fragment.appendChild(newNode.firstChild);
5976
      }
5977
      if (fragment.normalize) {
5978
        fragment.normalize();
5979
      }
5980
      return fragment;
5981
    }
5982
5983
    if (newNode.normalize) {
5984
      newNode.normalize();
5985
    }
5986
    return newNode;
5987
  }
5988
5989
  function _handleElement(oldNode, clearInternals) {
5990
    var rule,
5991
        newNode,
5992
        tagRules    = currentRules.tags,
5993
        nodeName    = oldNode.nodeName.toLowerCase(),
5994
        scopeName   = oldNode.scopeName;
5995
5996
    /**
5997
     * We already parsed that element
5998
     * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
5999
     */
6000
    if (oldNode._wysihtml5) {
6001
      return null;
6002
    }
6003
    oldNode._wysihtml5 = 1;
6004
6005
    if (oldNode.className === "wysihtml5-temp") {
6006
      return null;
6007
    }
6008
6009
    /**
6010
     * IE is the only browser who doesn't include the namespace in the
6011
     * nodeName, that's why we have to prepend it by ourselves
6012
     * scopeName is a proprietary IE feature
6013
     * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
6014
     */
6015
    if (scopeName && scopeName != "HTML") {
6016
      nodeName = scopeName + ":" + nodeName;
6017
    }
6018
    /**
6019
     * Repair node
6020
     * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
6021
     * 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
6022
     */
6023
    if ("outerHTML" in oldNode) {
6024
      if (!wysihtml5.browser.autoClosesUnclosedTags() &&
6025
          oldNode.nodeName === "P" &&
6026
          oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
6027
        nodeName = "div";
6028
      }
6029
    }
6030
6031
    if (nodeName in tagRules) {
6032
      rule = tagRules[nodeName];
6033
      if (!rule || rule.remove) {
6034
        return null;
6035
      } else if (rule.unwrap) {
6036
        return false;
6037
      }
6038
      rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
6039
    } else if (oldNode.firstChild) {
6040
      rule = { rename_tag: DEFAULT_NODE_NAME };
6041
    } else {
6042
      // Remove empty unknown elements
6043
      return null;
6044
    }
6045
6046
    newNode = oldNode.ownerDocument.createElement(rule.rename_tag || nodeName);
6047
    _handleAttributes(oldNode, newNode, rule, clearInternals);
6048
    _handleStyles(oldNode, newNode, rule);
6049
    // tests if type condition is met or node should be removed/unwrapped
6050
    if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
6051
      return (rule.remove_action && rule.remove_action == "unwrap") ? false : null;
6052
    }
6053
6054
    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...
6055
6056
    if (newNode.normalize) { newNode.normalize(); }
6057
    return newNode;
6058
  }
6059
6060
  function _testTypes(oldNode, rules, types, clearInternals) {
6061
    var definition, type;
6062
6063
    // do not interfere with placeholder span or pasting caret position is not maintained
6064
    if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml5-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
6065
      return true;
6066
    }
6067
6068
    for (type in types) {
6069
      if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
6070
        definition = rules.type_definitions[type];
6071
        if (_testType(oldNode, definition)) {
6072
          return true;
6073
        }
6074
      }
6075
    }
6076
    return false;
6077
  }
6078
6079
  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...
6080
      var i = a.length;
6081
      while (i--) {
6082
         if (a[i] === obj) {
6083
             return true;
6084
         }
6085
      }
6086
      return false;
6087
  }
6088
6089
  function _testType(oldNode, definition) {
6090
6091
    var nodeClasses = oldNode.getAttribute("class"),
6092
        nodeStyles =  oldNode.getAttribute("style"),
6093
        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...
6094
6095
    // test for methods
6096
    if (definition.methods) {
6097
      for (var m in definition.methods) {
6098
        if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
6099
6100
          if (typeCeckMethods[m](oldNode)) {
6101
            return true;
6102
          }
6103
        }
6104
      }
6105
    }
6106
6107
    // test for classes, if one found return true
6108
    if (nodeClasses && definition.classes) {
6109
      nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
6110
      classesLength = nodeClasses.length;
6111
      for (var i = 0; i < classesLength; i++) {
6112
        if (definition.classes[nodeClasses[i]]) {
6113
          return true;
6114
        }
6115
      }
6116
    }
6117
6118
    // test for styles, if one found return true
6119
    if (nodeStyles && definition.styles) {
6120
6121
      nodeStyles = nodeStyles.split(';');
6122
      for (s in definition.styles) {
6123
        if (definition.styles.hasOwnProperty(s)) {
6124
          for (var sp = nodeStyles.length; sp--;) {
6125
            styleProp = nodeStyles[sp].split(':');
6126
6127
            if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
6128
              if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml5.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
6129
                return true;
6130
              }
6131
            }
6132
          }
6133
        }
6134
      }
6135
    }
6136
6137
    // test for attributes in general against regex match
6138
    if (definition.attrs) {
6139
        for (a in definition.attrs) {
6140
            if (definition.attrs.hasOwnProperty(a)) {
6141
                attr = _getAttribute(oldNode, a);
6142
                if (typeof(attr) === "string") {
6143
                    if (attr.search(definition.attrs[a]) > -1) {
6144
                        return true;
6145
                    }
6146
                }
6147
            }
6148
        }
6149
    }
6150
    return false;
6151
  }
6152
6153
  function _handleStyles(oldNode, newNode, rule) {
6154
    var s;
6155
    if(rule && rule.keep_styles) {
6156
      for (s in rule.keep_styles) {
6157
        if (rule.keep_styles.hasOwnProperty(s)) {
6158
          if (s == "float") {
6159
            // IE compability
6160
            if (oldNode.style.styleFloat) {
6161
              newNode.style.styleFloat = oldNode.style.styleFloat;
6162
            }
6163
            if (oldNode.style.cssFloat) {
6164
              newNode.style.cssFloat = oldNode.style.cssFloat;
6165
            }
6166
           } else if (oldNode.style[s]) {
6167
             newNode.style[s] = oldNode.style[s];
6168
           }
6169
        }
6170
      }
6171
    }
6172
  }
6173
6174
  // TODO: refactor. Too long to read
6175
  function _handleAttributes(oldNode, newNode, rule, clearInternals) {
6176
    var attributes          = {},                         // fresh new set of attributes to set on newNode
6177
        setClass            = rule.set_class,             // classes to set
6178
        addClass            = rule.add_class,             // add classes based on existing attributes
6179
        addStyle            = rule.add_style,             // add styles based on existing attributes
6180
        setAttributes       = rule.set_attributes,        // attributes to set on the current node
6181
        checkAttributes     = rule.check_attributes,      // check/convert values of attributes
6182
        allowedClasses      = currentRules.classes,
6183
        i                   = 0,
6184
        classes             = [],
6185
        styles              = [],
6186
        newClasses          = [],
6187
        oldClasses          = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable oldClasses seems to be never used. Consider removing it.
Loading history...
6188
        classesLength,
6189
        newClassesLength,
0 ignored issues
show
Unused Code introduced by
The variable newClassesLength seems to be never used. Consider removing it.
Loading history...
6190
        currentClass,
6191
        newClass,
6192
        attributeName,
6193
        newAttributeValue,
6194
        method,
6195
        oldAttribute;
6196
6197
    if (setAttributes) {
6198
      attributes = wysihtml5.lang.object(setAttributes).clone();
6199
    }
6200
6201
    if (checkAttributes) {
6202
      for (attributeName in checkAttributes) {
6203
        method = attributeCheckMethods[checkAttributes[attributeName]];
6204
        if (!method) {
6205
          continue;
6206
        }
6207
        oldAttribute = _getAttribute(oldNode, attributeName);
6208
        if (oldAttribute || (attributeName === "alt" && oldNode.nodeName == "IMG")) {
6209
          newAttributeValue = method(oldAttribute);
6210
          if (typeof(newAttributeValue) === "string") {
6211
            attributes[attributeName] = newAttributeValue;
6212
          }
6213
        }
6214
      }
6215
    }
6216
6217
    if (setClass) {
6218
      classes.push(setClass);
6219
    }
6220
6221
    if (addClass) {
6222
      for (attributeName in addClass) {
6223
        method = addClassMethods[addClass[attributeName]];
6224
        if (!method) {
6225
          continue;
6226
        }
6227
        newClass = method(_getAttribute(oldNode, attributeName));
6228
        if (typeof(newClass) === "string") {
6229
          classes.push(newClass);
6230
        }
6231
      }
6232
    }
6233
6234
    if (addStyle) {
6235
      for (attributeName in addStyle) {
6236
        method = addStyleMethods[addStyle[attributeName]];
6237
        if (!method) {
6238
          continue;
6239
        }
6240
6241
        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...
6242
        if (typeof(newStyle) === "string") {
6243
          styles.push(newStyle);
6244
        }
6245
      }
6246
    }
6247
6248
6249
    if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
6250
      attributes["class"] = oldNode.getAttribute("class");
6251
    } else {
6252
      // make sure that wysihtml5 temp class doesn't get stripped out
6253
      if (!clearInternals) {
6254
        allowedClasses["_wysihtml5-temp-placeholder"] = 1;
6255
        allowedClasses["_rangySelectionBoundary"] = 1;
6256
        allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
6257
      }
6258
6259
      // add old classes last
6260
      oldClasses = oldNode.getAttribute("class");
6261
      if (oldClasses) {
6262
        classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
6263
      }
6264
      classesLength = classes.length;
6265
      for (; i<classesLength; i++) {
6266
        currentClass = classes[i];
6267
        if (allowedClasses[currentClass]) {
6268
          newClasses.push(currentClass);
6269
        }
6270
      }
6271
6272
      if (newClasses.length) {
6273
        attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
6274
      }
6275
    }
6276
6277
    // remove table selection class if present
6278
    if (attributes["class"] && clearInternals) {
6279
      attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
6280
      if ((/^\s*$/g).test(attributes["class"])) {
6281
        delete attributes["class"];
6282
      }
6283
    }
6284
6285
    if (styles.length) {
6286
      attributes["style"] = wysihtml5.lang.array(styles).unique().join(" ");
6287
    }
6288
6289
    // set attributes on newNode
6290
    for (attributeName in attributes) {
6291
      // Setting attributes can cause a js error in IE under certain circumstances
6292
      // eg. on a <img> under https when it's new attribute value is non-https
6293
      // TODO: Investigate this further and check for smarter handling
6294
      try {
6295
        newNode.setAttribute(attributeName, attributes[attributeName]);
6296
      } 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...
6297
    }
6298
6299
    // IE8 sometimes loses the width/height attributes when those are set before the "src"
6300
    // so we make sure to set them again
6301
    if (attributes.src) {
6302
      if (typeof(attributes.width) !== "undefined") {
6303
        newNode.setAttribute("width", attributes.width);
6304
      }
6305
      if (typeof(attributes.height) !== "undefined") {
6306
        newNode.setAttribute("height", attributes.height);
6307
      }
6308
    }
6309
  }
6310
6311
  /**
6312
   * IE gives wrong results for hasAttribute/getAttribute, for example:
6313
   *    var td = document.createElement("td");
6314
   *    td.getAttribute("rowspan"); // => "1" in IE
6315
   *
6316
   * Therefore we have to check the element's outerHTML for the attribute
6317
   */
6318
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
6319
  function _getAttribute(node, attributeName) {
6320
    attributeName = attributeName.toLowerCase();
6321
    var nodeName = node.nodeName;
6322
    if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
6323
      // Get 'src' attribute value via object property since this will always contain the
6324
      // full absolute url (http://...)
6325
      // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
6326
      // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
6327
      return node.src;
6328
    } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
6329
      // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
6330
      var outerHTML      = node.outerHTML.toLowerCase(),
6331
          // TODO: This might not work for attributes without value: <input disabled>
6332
          hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
6333
6334
      return hasAttribute ? node.getAttribute(attributeName) : null;
6335
    } else{
6336
      return node.getAttribute(attributeName);
6337
    }
6338
  }
6339
6340
  /**
6341
   * Check whether the given node is a proper loaded image
6342
   * FIXME: Returns undefined when unknown (Chrome, Safari)
6343
   */
6344
  function _isLoadedImage(node) {
6345
    try {
6346
      return node.complete && !node.mozMatchesSelector(":-moz-broken");
6347
    } catch(e) {
6348
      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...
6349
        return true;
6350
      }
6351
    }
6352
  }
6353
6354
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
6355
  function _handleText(oldNode) {
6356
    var nextSibling = oldNode.nextSibling;
6357
    if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
6358
      // Concatenate text nodes
6359
      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...
6360
    } else {
6361
      // \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
6362
      var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
6363
      return oldNode.ownerDocument.createTextNode(data);
6364
    }
6365
  }
6366
6367
  function _handleComment(oldNode) {
6368
    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...
6369
      return oldNode.ownerDocument.createComment(oldNode.nodeValue);
6370
    }
6371
  }
6372
6373
  // ------------ attribute checks ------------ \\
6374
  var attributeCheckMethods = {
6375
    url: (function() {
6376
      var REG_EXP = /^https?:\/\//i;
6377
      return function(attributeValue) {
6378
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6379
          return null;
6380
        }
6381
        return attributeValue.replace(REG_EXP, function(match) {
6382
          return match.toLowerCase();
6383
        });
6384
      };
6385
    })(),
6386
6387
    src: (function() {
6388
      var REG_EXP = /^(\/|https?:\/\/)/i;
6389
      return function(attributeValue) {
6390
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6391
          return null;
6392
        }
6393
        return attributeValue.replace(REG_EXP, function(match) {
6394
          return match.toLowerCase();
6395
        });
6396
      };
6397
    })(),
6398
6399
    href: (function() {
6400
      var REG_EXP = /^(#|\/|https?:\/\/|mailto:)/i;
6401
      return function(attributeValue) {
6402
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6403
          return null;
6404
        }
6405
        return attributeValue.replace(REG_EXP, function(match) {
6406
          return match.toLowerCase();
6407
        });
6408
      };
6409
    })(),
6410
6411
    alt: (function() {
6412
      var REG_EXP = /[^ a-z0-9_\-]/gi;
6413
      return function(attributeValue) {
6414
        if (!attributeValue) {
6415
          return "";
6416
        }
6417
        return attributeValue.replace(REG_EXP, "");
6418
      };
6419
    })(),
6420
6421
    numbers: (function() {
6422
      var REG_EXP = /\D/g;
6423
      return function(attributeValue) {
6424
        attributeValue = (attributeValue || "").replace(REG_EXP, "");
6425
        return attributeValue || null;
6426
      };
6427
    })(),
6428
6429
    any: (function() {
6430
      return function(attributeValue) {
6431
        return attributeValue;
6432
      };
6433
    })()
6434
  };
6435
6436
  // ------------ style converter (converts an html attribute to a style) ------------ \\
6437
  var addStyleMethods = {
6438
    align_text: (function() {
6439
      var mapping = {
6440
        left:     "text-align: left;",
6441
        right:    "text-align: right;",
6442
        center:   "text-align: center;"
6443
      };
6444
      return function(attributeValue) {
6445
        return mapping[String(attributeValue).toLowerCase()];
6446
      };
6447
    })(),
6448
  };
6449
6450
  // ------------ class converter (converts an html attribute to a class name) ------------ \\
6451
  var addClassMethods = {
6452
    align_img: (function() {
6453
      var mapping = {
6454
        left:   "wysiwyg-float-left",
6455
        right:  "wysiwyg-float-right"
6456
      };
6457
      return function(attributeValue) {
6458
        return mapping[String(attributeValue).toLowerCase()];
6459
      };
6460
    })(),
6461
6462
    align_text: (function() {
6463
      var mapping = {
6464
        left:     "wysiwyg-text-align-left",
6465
        right:    "wysiwyg-text-align-right",
6466
        center:   "wysiwyg-text-align-center",
6467
        justify:  "wysiwyg-text-align-justify"
6468
      };
6469
      return function(attributeValue) {
6470
        return mapping[String(attributeValue).toLowerCase()];
6471
      };
6472
    })(),
6473
6474
    clear_br: (function() {
6475
      var mapping = {
6476
        left:   "wysiwyg-clear-left",
6477
        right:  "wysiwyg-clear-right",
6478
        both:   "wysiwyg-clear-both",
6479
        all:    "wysiwyg-clear-both"
6480
      };
6481
      return function(attributeValue) {
6482
        return mapping[String(attributeValue).toLowerCase()];
6483
      };
6484
    })(),
6485
6486
    size_font: (function() {
6487
      var mapping = {
6488
        "1": "wysiwyg-font-size-xx-small",
6489
        "2": "wysiwyg-font-size-small",
6490
        "3": "wysiwyg-font-size-medium",
6491
        "4": "wysiwyg-font-size-large",
6492
        "5": "wysiwyg-font-size-x-large",
6493
        "6": "wysiwyg-font-size-xx-large",
6494
        "7": "wysiwyg-font-size-xx-large",
6495
        "-": "wysiwyg-font-size-smaller",
6496
        "+": "wysiwyg-font-size-larger"
6497
      };
6498
      return function(attributeValue) {
6499
        return mapping[String(attributeValue).charAt(0)];
6500
      };
6501
    })()
6502
  };
6503
6504
  // checks if element is possibly visible
6505
  var typeCeckMethods = {
6506
    has_visible_contet: (function() {
6507
      var txt,
6508
          isVisible = false,
0 ignored issues
show
Unused Code introduced by
The variable isVisible seems to be never used. Consider removing it.
Loading history...
6509
          visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
6510
                             'style', 'table', 'iframe', 'object', 'embed', 'audio',
6511
                             'svg', 'input', 'button', 'select','textarea', 'canvas'];
6512
6513
      return function(el) {
6514
6515
        // has visible innertext. so is visible
6516
        txt = (el.innerText || el.textContent).replace(/\s/g, '');
6517
        if (txt && txt.length > 0) {
6518
          return true;
6519
        }
6520
6521
        // matches list of visible dimensioned elements
6522
        for (var i = visibleElements.length; i--;) {
6523
          if (el.querySelector(visibleElements[i])) {
6524
            return true;
6525
          }
6526
        }
6527
6528
        // try to measure dimesions in last resort. (can find only of elements in dom)
6529
        if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
6530
          return true;
6531
        }
6532
6533
        return false;
6534
      };
6535
    })()
6536
  };
6537
6538
  return parse;
6539
})();
6540
;/**
6541
 * Checks for empty text node childs and removes them
6542
 *
6543
 * @param {Element} node The element in which to cleanup
6544
 * @example
6545
 *    wysihtml5.dom.removeEmptyTextNodes(element);
6546
 */
6547
wysihtml5.dom.removeEmptyTextNodes = function(node) {
6548
  var childNode,
6549
      childNodes        = wysihtml5.lang.array(node.childNodes).get(),
6550
      childNodesLength  = childNodes.length,
6551
      i                 = 0;
6552
  for (; i<childNodesLength; i++) {
6553
    childNode = childNodes[i];
6554
    if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
6555
      childNode.parentNode.removeChild(childNode);
6556
    }
6557
  }
6558
};
6559
;/**
6560
 * Renames an element (eg. a <div> to a <p>) and keeps its childs
6561
 *
6562
 * @param {Element} element The list element which should be renamed
6563
 * @param {Element} newNodeName The desired tag name
6564
 *
6565
 * @example
6566
 *    <!-- Assume the following dom: -->
6567
 *    <ul id="list">
6568
 *      <li>eminem</li>
6569
 *      <li>dr. dre</li>
6570
 *      <li>50 Cent</li>
6571
 *    </ul>
6572
 *
6573
 *    <script>
6574
 *      wysihtml5.dom.renameElement(document.getElementById("list"), "ol");
6575
 *    </script>
6576
 *
6577
 *    <!-- Will result in: -->
6578
 *    <ol>
6579
 *      <li>eminem</li>
6580
 *      <li>dr. dre</li>
6581
 *      <li>50 Cent</li>
6582
 *    </ol>
6583
 */
6584
wysihtml5.dom.renameElement = function(element, newNodeName) {
6585
  var newElement = element.ownerDocument.createElement(newNodeName),
6586
      firstChild;
6587
  while (firstChild = element.firstChild) {
6588
    newElement.appendChild(firstChild);
6589
  }
6590
  wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
6591
  element.parentNode.replaceChild(newElement, element);
6592
  return newElement;
6593
};
6594
;/**
6595
 * Takes an element, removes it and replaces it with it's childs
6596
 *
6597
 * @param {Object} node The node which to replace with it's child nodes
6598
 * @example
6599
 *    <div id="foo">
6600
 *      <span>hello</span>
6601
 *    </div>
6602
 *    <script>
6603
 *      // Remove #foo and replace with it's children
6604
 *      wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo"));
6605
 *    </script>
6606
 */
6607
wysihtml5.dom.replaceWithChildNodes = function(node) {
6608
  if (!node.parentNode) {
6609
    return;
6610
  }
6611
6612
  if (!node.firstChild) {
6613
    node.parentNode.removeChild(node);
6614
    return;
6615
  }
6616
6617
  var fragment = node.ownerDocument.createDocumentFragment();
6618
  while (node.firstChild) {
6619
    fragment.appendChild(node.firstChild);
6620
  }
6621
  node.parentNode.replaceChild(fragment, node);
6622
  node = fragment = null;
0 ignored issues
show
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...
Unused Code introduced by
The assignment to variable node seems to be never used. Consider removing it.
Loading history...
6623
};
6624
;/**
6625
 * Unwraps an unordered/ordered list
6626
 *
6627
 * @param {Element} element The list element which should be unwrapped
6628
 *
6629
 * @example
6630
 *    <!-- Assume the following dom: -->
6631
 *    <ul id="list">
6632
 *      <li>eminem</li>
6633
 *      <li>dr. dre</li>
6634
 *      <li>50 Cent</li>
6635
 *    </ul>
6636
 *
6637
 *    <script>
6638
 *      wysihtml5.dom.resolveList(document.getElementById("list"));
6639
 *    </script>
6640
 *
6641
 *    <!-- Will result in: -->
6642
 *    eminem<br>
6643
 *    dr. dre<br>
6644
 *    50 Cent<br>
6645
 */
6646
(function(dom) {
6647
  function _isBlockElement(node) {
6648
    return dom.getStyle("display").from(node) === "block";
6649
  }
6650
6651
  function _isLineBreak(node) {
6652
    return node.nodeName === "BR";
6653
  }
6654
6655
  function _appendLineBreak(element) {
6656
    var lineBreak = element.ownerDocument.createElement("br");
6657
    element.appendChild(lineBreak);
6658
  }
6659
6660
  function resolveList(list, useLineBreaks) {
6661
    if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
6662
      return;
6663
    }
6664
6665
    var doc             = list.ownerDocument,
6666
        fragment        = doc.createDocumentFragment(),
6667
        previousSibling = wysihtml5.dom.domNode(list).prev({ignoreBlankTexts: true}),
6668
        firstChild,
6669
        lastChild,
6670
        isLastChild,
6671
        shouldAppendLineBreak,
6672
        paragraph,
6673
        listItem;
6674
6675
    if (useLineBreaks) {
6676
      // Insert line break if list is after a non-block element
6677
      if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) {
6678
        _appendLineBreak(fragment);
6679
      }
6680
6681
      while (listItem = (list.firstElementChild || list.firstChild)) {
6682
        lastChild = listItem.lastChild;
6683
        while (firstChild = listItem.firstChild) {
6684
          isLastChild           = firstChild === lastChild;
6685
          // This needs to be done before appending it to the fragment, as it otherwise will lose style information
6686
          shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
6687
          fragment.appendChild(firstChild);
6688
          if (shouldAppendLineBreak) {
6689
            _appendLineBreak(fragment);
6690
          }
6691
        }
6692
6693
        listItem.parentNode.removeChild(listItem);
6694
      }
6695
    } else {
6696
      while (listItem = (list.firstElementChild || list.firstChild)) {
6697
        if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
6698
          while (firstChild = listItem.firstChild) {
6699
            fragment.appendChild(firstChild);
6700
          }
6701
        } else {
6702
          paragraph = doc.createElement("p");
6703
          while (firstChild = listItem.firstChild) {
6704
            paragraph.appendChild(firstChild);
6705
          }
6706
          fragment.appendChild(paragraph);
6707
        }
6708
        listItem.parentNode.removeChild(listItem);
6709
      }
6710
    }
6711
6712
    list.parentNode.replaceChild(fragment, list);
6713
  }
6714
6715
  dom.resolveList = resolveList;
6716
})(wysihtml5.dom);
6717
;/**
6718
 * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
6719
 *
6720
 * Browser Compatibility:
6721
 *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
6722
 *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
6723
 *
6724
 * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
6725
 *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
6726
 *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
6727
 *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
6728
 *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
6729
 *      can do anything as if the sandbox attribute wasn't set
6730
 *
6731
 * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
6732
 * @param {Object} [config] Optional parameters
6733
 *
6734
 * @example
6735
 *    new wysihtml5.dom.Sandbox(function(sandbox) {
6736
 *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
6737
 *    });
6738
 */
6739
(function(wysihtml5) {
6740
  var /**
6741
       * Default configuration
6742
       */
6743
      doc                 = document,
6744
      /**
6745
       * Properties to unset/protect on the window object
6746
       */
6747
      windowProperties    = [
6748
        "parent", "top", "opener", "frameElement", "frames",
6749
        "localStorage", "globalStorage", "sessionStorage", "indexedDB"
6750
      ],
6751
      /**
6752
       * Properties on the window object which are set to an empty function
6753
       */
6754
      windowProperties2   = [
6755
        "open", "close", "openDialog", "showModalDialog",
6756
        "alert", "confirm", "prompt",
6757
        "openDatabase", "postMessage",
6758
        "XMLHttpRequest", "XDomainRequest"
6759
      ],
6760
      /**
6761
       * Properties to unset/protect on the document object
6762
       */
6763
      documentProperties  = [
6764
        "referrer",
6765
        "write", "open", "close"
6766
      ];
6767
6768
  wysihtml5.dom.Sandbox = Base.extend(
6769
    /** @scope wysihtml5.dom.Sandbox.prototype */ {
6770
6771
    constructor: function(readyCallback, config) {
6772
      this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
6773
      this.config   = wysihtml5.lang.object({}).merge(config).get();
6774
      this.editableArea   = this._createIframe();
6775
    },
6776
6777
    insertInto: function(element) {
6778
      if (typeof(element) === "string") {
6779
        element = doc.getElementById(element);
6780
      }
6781
6782
      element.appendChild(this.editableArea);
6783
    },
6784
6785
    getIframe: function() {
6786
      return this.editableArea;
6787
    },
6788
6789
    getWindow: function() {
6790
      this._readyError();
6791
    },
6792
6793
    getDocument: function() {
6794
      this._readyError();
6795
    },
6796
6797
    destroy: function() {
6798
      var iframe = this.getIframe();
6799
      iframe.parentNode.removeChild(iframe);
6800
    },
6801
6802
    _readyError: function() {
6803
      throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");
6804
    },
6805
6806
    /**
6807
     * Creates the sandbox iframe
6808
     *
6809
     * Some important notes:
6810
     *  - We can't use HTML5 sandbox for now:
6811
     *    setting it causes that the iframe's dom can't be accessed from the outside
6812
     *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
6813
     *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
6814
     *    In order to make this happen we need to set the "allow-scripts" flag.
6815
     *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
6816
     *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
6817
     *  - IE needs to have the security="restricted" attribute set before the iframe is
6818
     *    inserted into the dom tree
6819
     *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
6820
     *    though it supports it
6821
     *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
6822
     *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
6823
     *    on the onreadystatechange event
6824
     */
6825
    _createIframe: function() {
6826
      var that   = this,
6827
          iframe = doc.createElement("iframe");
6828
      iframe.className = "wysihtml5-sandbox";
6829
      wysihtml5.dom.setAttributes({
6830
        "security":           "restricted",
6831
        "allowtransparency":  "true",
6832
        "frameborder":        0,
6833
        "width":              0,
6834
        "height":             0,
6835
        "marginwidth":        0,
6836
        "marginheight":       0
6837
      }).on(iframe);
6838
6839
      // Setting the src like this prevents ssl warnings in IE6
6840
      if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
6841
        iframe.src = "javascript:'<html></html>'";
6842
      }
6843
6844
      iframe.onload = function() {
6845
        iframe.onreadystatechange = iframe.onload = null;
6846
        that._onLoadIframe(iframe);
6847
      };
6848
6849
      iframe.onreadystatechange = function() {
6850
        if (/loaded|complete/.test(iframe.readyState)) {
6851
          iframe.onreadystatechange = iframe.onload = null;
6852
          that._onLoadIframe(iframe);
6853
        }
6854
      };
6855
6856
      return iframe;
6857
    },
6858
6859
    /**
6860
     * Callback for when the iframe has finished loading
6861
     */
6862
    _onLoadIframe: function(iframe) {
6863
      // don't resume when the iframe got unloaded (eg. by removing it from the dom)
6864
      if (!wysihtml5.dom.contains(doc.documentElement, iframe)) {
6865
        return;
6866
      }
6867
6868
      var that           = this,
6869
          iframeWindow   = iframe.contentWindow,
6870
          iframeDocument = iframe.contentWindow.document,
6871
          charset        = doc.characterSet || doc.charset || "utf-8",
6872
          sandboxHtml    = this._getHtml({
6873
            charset:      charset,
6874
            stylesheets:  this.config.stylesheets
6875
          });
6876
6877
      // Create the basic dom tree including proper DOCTYPE and charset
6878
      iframeDocument.open("text/html", "replace");
6879
      iframeDocument.write(sandboxHtml);
6880
      iframeDocument.close();
6881
6882
      this.getWindow = function() { return iframe.contentWindow; };
6883
      this.getDocument = function() { return iframe.contentWindow.document; };
6884
6885
      // Catch js errors and pass them to the parent's onerror event
6886
      // addEventListener("error") doesn't work properly in some browsers
6887
      // TODO: apparently this doesn't work in IE9!
6888
      iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
6889
        throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
6890
      };
6891
6892
      if (!wysihtml5.browser.supportsSandboxedIframes()) {
6893
        // Unset a bunch of sensitive variables
6894
        // Please note: This isn't hack safe!
6895
        // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
6896
        // IE is secure though, which is the most important thing, since IE is the only browser, who
6897
        // takes over scripts & styles into contentEditable elements when copied from external websites
6898
        // or applications (Microsoft Word, ...)
6899
        var i, length;
6900
        for (i=0, length=windowProperties.length; i<length; i++) {
6901
          this._unset(iframeWindow, windowProperties[i]);
6902
        }
6903
        for (i=0, length=windowProperties2.length; i<length; i++) {
6904
          this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION);
6905
        }
6906
        for (i=0, length=documentProperties.length; i<length; i++) {
6907
          this._unset(iframeDocument, documentProperties[i]);
6908
        }
6909
        // This doesn't work in Safari 5
6910
        // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
6911
        this._unset(iframeDocument, "cookie", "", true);
6912
      }
6913
6914
      this.loaded = true;
6915
6916
      // Trigger the callback
6917
      setTimeout(function() { that.callback(that); }, 0);
6918
    },
6919
6920
    _getHtml: function(templateVars) {
6921
      var stylesheets = templateVars.stylesheets,
6922
          html        = "",
6923
          i           = 0,
6924
          length;
6925
      stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
6926
      if (stylesheets) {
6927
        length = stylesheets.length;
6928
        for (; i<length; i++) {
6929
          html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
6930
        }
6931
      }
6932
      templateVars.stylesheets = html;
6933
6934
      return wysihtml5.lang.string(
6935
        '<!DOCTYPE html><html><head>'
6936
        + '<meta charset="#{charset}">#{stylesheets}</head>'
6937
        + '<body></body></html>'
6938
      ).interpolate(templateVars);
6939
    },
6940
6941
    /**
6942
     * Method to unset/override existing variables
6943
     * @example
6944
     *    // Make cookie unreadable and unwritable
6945
     *    this._unset(document, "cookie", "", true);
6946
     */
6947
    _unset: function(object, property, value, setter) {
6948
      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...
6949
6950
      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...
6951
      if (setter) {
6952
        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...
6953
      }
6954
6955
      if (!wysihtml5.browser.crashesWhenDefineProperty(property)) {
6956
        try {
6957
          var config = {
6958
            get: function() { return value; }
6959
          };
6960
          if (setter) {
6961
            config.set = function() {};
6962
          }
6963
          Object.defineProperty(object, property, config);
6964
        } 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...
6965
      }
6966
    }
6967
  });
6968
})(wysihtml5);
6969
;(function(wysihtml5) {
6970
  var doc = document;
6971
  wysihtml5.dom.ContentEditableArea = Base.extend({
6972
      getContentEditable: function() {
6973
        return this.element;
6974
      },
6975
6976
      getWindow: function() {
6977
        return this.element.ownerDocument.defaultView;
6978
      },
6979
6980
      getDocument: function() {
6981
        return this.element.ownerDocument;
6982
      },
6983
6984
      constructor: function(readyCallback, config, contentEditable) {
6985
        this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
6986
        this.config   = wysihtml5.lang.object({}).merge(config).get();
6987
        if (contentEditable) {
6988
            this.element = this._bindElement(contentEditable);
6989
        } else {
6990
            this.element = this._createElement();
6991
        }
6992
      },
6993
6994
      // creates a new contenteditable and initiates it
6995
      _createElement: function() {
6996
        var element = doc.createElement("div");
6997
        element.className = "wysihtml5-sandbox";
6998
        this._loadElement(element);
6999
        return element;
7000
      },
7001
7002
      // initiates an allready existent contenteditable
7003
      _bindElement: function(contentEditable) {
7004
        contentEditable.className = (contentEditable.className && contentEditable.className != '') ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
7005
        this._loadElement(contentEditable, true);
7006
        return contentEditable;
7007
      },
7008
7009
      _loadElement: function(element, contentExists) {
7010
          var that = this;
7011
        if (!contentExists) {
7012
            var sandboxHtml = this._getHtml();
7013
            element.innerHTML = sandboxHtml;
7014
        }
7015
7016
        this.getWindow = function() { return element.ownerDocument.defaultView; };
7017
        this.getDocument = function() { return element.ownerDocument; };
7018
7019
        // Catch js errors and pass them to the parent's onerror event
7020
        // addEventListener("error") doesn't work properly in some browsers
7021
        // TODO: apparently this doesn't work in IE9!
7022
        // TODO: figure out and bind the errors logic for contenteditble mode
7023
        /*iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
7024
          throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
7025
        }
7026
        */
7027
        this.loaded = true;
7028
        // Trigger the callback
7029
        setTimeout(function() { that.callback(that); }, 0);
7030
      },
7031
7032
      _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...
7033
        return '';
7034
      }
7035
7036
  });
7037
})(wysihtml5);
7038
;(function() {
7039
  var mapping = {
7040
    "className": "class"
7041
  };
7042
  wysihtml5.dom.setAttributes = function(attributes) {
7043
    return {
7044
      on: function(element) {
7045
        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...
7046
          element.setAttribute(mapping[i] || i, attributes[i]);
7047
        }
7048
      }
7049
    };
7050
  };
7051
})();
7052
;wysihtml5.dom.setStyles = function(styles) {
7053
  return {
7054
    on: function(element) {
7055
      var style = element.style;
7056
      if (typeof(styles) === "string") {
7057
        style.cssText += ";" + styles;
7058
        return;
7059
      }
7060
      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...
7061
        if (i === "float") {
7062
          style.cssFloat = styles[i];
7063
          style.styleFloat = styles[i];
7064
        } else {
7065
          style[i] = styles[i];
7066
        }
7067
      }
7068
    }
7069
  };
7070
};
7071
;/**
7072
 * Simulate HTML5 placeholder attribute
7073
 *
7074
 * Needed since
7075
 *    - div[contentEditable] elements don't support it
7076
 *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all
7077
 *
7078
 * @param {Object} parent Instance of main wysihtml5.Editor class
7079
 * @param {Element} view Instance of wysihtml5.views.* class
7080
 * @param {String} placeholderText
7081
 *
7082
 * @example
7083
 *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
7084
 */
7085
(function(dom) {
7086
  dom.simulatePlaceholder = function(editor, view, placeholderText) {
7087
    var CLASS_NAME = "placeholder",
7088
        unset = function() {
7089
          var composerIsVisible   = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
7090
          if (view.hasPlaceholderSet()) {
7091
            view.clear();
7092
            view.element.focus();
7093
            if (composerIsVisible ) {
7094
              setTimeout(function() {
7095
                var sel = view.selection.getSelection();
7096
                if (!sel.focusNode || !sel.anchorNode) {
7097
                  view.selection.selectNode(view.element.firstChild || view.element);
7098
                }
7099
              }, 0);
7100
            }
7101
          }
7102
          view.placeholderSet = false;
7103
          dom.removeClass(view.element, CLASS_NAME);
7104
        },
7105
        set = function() {
7106
          if (view.isEmpty()) {
7107
            view.placeholderSet = true;
7108
            view.setValue(placeholderText);
7109
            dom.addClass(view.element, CLASS_NAME);
7110
          }
7111
        };
7112
7113
    editor
7114
      .on("set_placeholder", set)
7115
      .on("unset_placeholder", unset)
7116
      .on("focus:composer", unset)
7117
      .on("paste:composer", unset)
7118
      .on("blur:composer", set);
7119
7120
    set();
7121
  };
7122
})(wysihtml5.dom);
7123
;(function(dom) {
7124
  var documentElement = document.documentElement;
7125
  if ("textContent" in documentElement) {
7126
    dom.setTextContent = function(element, text) {
7127
      element.textContent = text;
7128
    };
7129
7130
    dom.getTextContent = function(element) {
7131
      return element.textContent;
7132
    };
7133
  } else if ("innerText" in documentElement) {
7134
    dom.setTextContent = function(element, text) {
7135
      element.innerText = text;
7136
    };
7137
7138
    dom.getTextContent = function(element) {
7139
      return element.innerText;
7140
    };
7141
  } else {
7142
    dom.setTextContent = function(element, text) {
7143
      element.nodeValue = text;
7144
    };
7145
7146
    dom.getTextContent = function(element) {
7147
      return element.nodeValue;
7148
    };
7149
  }
7150
})(wysihtml5.dom);
7151
7152
;/**
7153
 * Get a set of attribute from one element
7154
 *
7155
 * IE gives wrong results for hasAttribute/getAttribute, for example:
7156
 *    var td = document.createElement("td");
7157
 *    td.getAttribute("rowspan"); // => "1" in IE
7158
 *
7159
 * Therefore we have to check the element's outerHTML for the attribute
7160
*/
7161
7162
wysihtml5.dom.getAttribute = function(node, attributeName) {
7163
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
7164
  attributeName = attributeName.toLowerCase();
7165
  var nodeName = node.nodeName;
7166
  if (nodeName == "IMG" && attributeName == "src" && _isLoadedImage(node) === true) {
7167
    // Get 'src' attribute value via object property since this will always contain the
7168
    // full absolute url (http://...)
7169
    // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
7170
    // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
7171
    return node.src;
7172
  } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
7173
    // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
7174
    var outerHTML      = node.outerHTML.toLowerCase(),
7175
        // TODO: This might not work for attributes without value: <input disabled>
7176
        hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
7177
7178
    return hasAttribute ? node.getAttribute(attributeName) : null;
7179
  } else{
7180
    return node.getAttribute(attributeName);
7181
  }
7182
};
7183
;(function(wysihtml5) {
7184
7185
    var api = wysihtml5.dom;
7186
7187
    var MapCell = function(cell) {
7188
      this.el = cell;
7189
      this.isColspan= false;
7190
      this.isRowspan= false;
7191
      this.firstCol= true;
7192
      this.lastCol= true;
7193
      this.firstRow= true;
7194
      this.lastRow= true;
7195
      this.isReal= true;
7196
      this.spanCollection= [];
7197
      this.modified = false;
7198
    };
7199
7200
    var TableModifyerByCell = function (cell, table) {
7201
        if (cell) {
7202
            this.cell = cell;
7203
            this.table = api.getParentElement(cell, { nodeName: ["TABLE"] });
7204
        } else if (table) {
7205
            this.table = table;
7206
            this.cell = this.table.querySelectorAll('th, td')[0];
7207
        }
7208
    };
7209
7210
    function queryInList(list, query) {
7211
        var ret = [],
7212
            q;
7213
        for (var e = 0, len = list.length; e < len; e++) {
7214
            q = list[e].querySelectorAll(query);
7215
            if (q) {
7216
                for(var i = q.length; i--; ret.unshift(q[i]));
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...
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...
7217
            }
7218
        }
7219
        return ret;
7220
    }
7221
7222
    function removeElement(el) {
7223
        el.parentNode.removeChild(el);
7224
    }
7225
7226
    function insertAfter(referenceNode, newNode) {
7227
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
7228
    }
7229
7230
    function nextNode(node, tag) {
7231
        var element = node.nextSibling;
7232
        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...
7233
            element = element.nextSibling;
7234
            if (!tag || tag == element.tagName.toLowerCase()) {
7235
                return element;
7236
            }
7237
        }
7238
        return null;
7239
    }
7240
7241
    TableModifyerByCell.prototype = {
7242
7243
        addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) {
7244
            var spanCollect = [],
7245
                rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0),
7246
                cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0);
7247
7248
            for (var rr = r; rr <= rmax; rr++) {
7249
                if (typeof map[rr] == "undefined") { map[rr] = []; }
7250
                for (var cc = c; cc <= cmax; cc++) {
7251
                    map[rr][cc] = new MapCell(cell);
7252
                    map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1);
7253
                    map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1);
7254
                    map[rr][cc].firstCol = cc == c;
7255
                    map[rr][cc].lastCol = cc == cmax;
7256
                    map[rr][cc].firstRow = rr == r;
7257
                    map[rr][cc].lastRow = rr == rmax;
7258
                    map[rr][cc].isReal = cc == c && rr == r;
7259
                    map[rr][cc].spanCollection = spanCollect;
7260
7261
                    spanCollect.push(map[rr][cc]);
7262
                }
7263
            }
7264
        },
7265
7266
        setCellAsModified: function(cell) {
7267
            cell.modified = true;
7268
            if (cell.spanCollection.length > 0) {
7269
              for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) {
7270
                cell.spanCollection[s].modified = true;
7271
              }
7272
            }
7273
        },
7274
7275
        setTableMap: function() {
7276
            var map = [];
7277
            var tableRows = this.getTableRows(),
7278
                ridx, row, cells, cidx, cell,
7279
                c,
7280
                cspan, rspan;
7281
7282
            for (ridx = 0; ridx < tableRows.length; ridx++) {
7283
                row = tableRows[ridx];
7284
                cells = this.getRowCells(row);
7285
                c = 0;
7286
                if (typeof map[ridx] == "undefined") { map[ridx] = []; }
7287
                for (cidx = 0; cidx < cells.length; cidx++) {
7288
                    cell = cells[cidx];
7289
7290
                    // If cell allready set means it is set by col or rowspan,
7291
                    // so increase cols index until free col is found
7292
                    while (typeof map[ridx][c] != "undefined") { c++; }
7293
7294
                    cspan = api.getAttribute(cell, 'colspan');
7295
                    rspan = api.getAttribute(cell, 'rowspan');
7296
7297
                    if (cspan || rspan) {
7298
                        this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan);
7299
                        c = c + ((cspan) ? parseInt(cspan, 10) : 1);
7300
                    } else {
7301
                        map[ridx][c] = new MapCell(cell);
7302
                        c++;
7303
                    }
7304
                }
7305
            }
7306
            this.map = map;
7307
            return map;
7308
        },
7309
7310
        getRowCells: function(row) {
7311
            var inlineTables = this.table.querySelectorAll('table'),
7312
                inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [],
7313
                allCells = row.querySelectorAll('th, td'),
7314
                tableCells = (inlineCells.length > 0) ? wysihtml5.lang.array(allCells).without(inlineCells) : allCells;
7315
7316
            return tableCells;
7317
        },
7318
7319
        getTableRows: function() {
7320
          var inlineTables = this.table.querySelectorAll('table'),
7321
              inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [],
7322
              allRows = this.table.querySelectorAll('tr'),
7323
              tableRows = (inlineRows.length > 0) ? wysihtml5.lang.array(allRows).without(inlineRows) : allRows;
7324
7325
          return tableRows;
7326
        },
7327
7328
        getMapIndex: function(cell) {
7329
          var r_length = this.map.length,
7330
              c_length = (this.map && this.map[0]) ? this.map[0].length : 0;
7331
7332
          for (var r_idx = 0;r_idx < r_length; r_idx++) {
7333
              for (var c_idx = 0;c_idx < c_length; c_idx++) {
7334
                  if (this.map[r_idx][c_idx].el === cell) {
7335
                      return {'row': r_idx, 'col': c_idx};
7336
                  }
7337
              }
7338
          }
7339
          return false;
7340
        },
7341
7342
        getElementAtIndex: function(idx) {
7343
            this.setTableMap();
7344
            if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) {
7345
                return this.map[idx.row][idx.col].el;
7346
            }
7347
            return null;
7348
        },
7349
7350
        getMapElsTo: function(to_cell) {
7351
            var els = [];
7352
            this.setTableMap();
7353
            this.idx_start = this.getMapIndex(this.cell);
7354
            this.idx_end = this.getMapIndex(to_cell);
7355
7356
            // switch indexes if start is bigger than end
7357
            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)) {
7358
                var temp_idx = this.idx_start;
7359
                this.idx_start = this.idx_end;
7360
                this.idx_end = temp_idx;
7361
            }
7362
            if (this.idx_start.col > this.idx_end.col) {
7363
                var temp_cidx = this.idx_start.col;
7364
                this.idx_start.col = this.idx_end.col;
7365
                this.idx_end.col = temp_cidx;
7366
            }
7367
7368
            if (this.idx_start != null && this.idx_end != null) {
0 ignored issues
show
Best Practice introduced by
Comparing this.idx_end to null using the != operator is not safe. Consider using !== instead.
Loading history...
Best Practice introduced by
Comparing this.idx_start to null using the != operator is not safe. Consider using !== instead.
Loading history...
7369
                for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7370
                    for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7371
                        els.push(this.map[row][col].el);
7372
                    }
7373
                }
7374
            }
7375
            return els;
7376
        },
7377
7378
        orderSelectionEnds: function(secondcell) {
7379
            this.setTableMap();
7380
            this.idx_start = this.getMapIndex(this.cell);
7381
            this.idx_end = this.getMapIndex(secondcell);
7382
7383
            // switch indexes if start is bigger than end
7384
            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)) {
7385
                var temp_idx = this.idx_start;
7386
                this.idx_start = this.idx_end;
7387
                this.idx_end = temp_idx;
7388
            }
7389
            if (this.idx_start.col > this.idx_end.col) {
7390
                var temp_cidx = this.idx_start.col;
7391
                this.idx_start.col = this.idx_end.col;
7392
                this.idx_end.col = temp_cidx;
7393
            }
7394
7395
            return {
7396
                "start": this.map[this.idx_start.row][this.idx_start.col].el,
7397
                "end": this.map[this.idx_end.row][this.idx_end.col].el
7398
            };
7399
        },
7400
7401
        createCells: function(tag, nr, attrs) {
7402
            var doc = this.table.ownerDocument,
7403
                frag = doc.createDocumentFragment(),
7404
                cell;
7405
            for (var i = 0; i < nr; i++) {
7406
                cell = doc.createElement(tag);
7407
7408
                if (attrs) {
7409
                    for (var attr in attrs) {
7410
                        if (attrs.hasOwnProperty(attr)) {
7411
                            cell.setAttribute(attr, attrs[attr]);
7412
                        }
7413
                    }
7414
                }
7415
7416
                // add non breaking space
7417
                cell.appendChild(document.createTextNode("\u00a0"));
7418
7419
                frag.appendChild(cell);
7420
            }
7421
            return frag;
7422
        },
7423
7424
        // 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
7425
        correctColIndexForUnreals: function(col, row) {
7426
            var r = this.map[row],
7427
                corrIdx = -1;
7428
            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...
7429
                if (r[i].isReal){
7430
                    corrIdx++;
7431
                }
7432
            }
7433
            return corrIdx;
7434
        },
7435
7436
        getLastNewCellOnRow: function(row, rowLimit) {
7437
            var cells = this.getRowCells(row),
7438
                cell, idx;
7439
7440
            for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) {
7441
                cell = cells[cidx];
7442
                idx = this.getMapIndex(cell);
7443
                if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) {
7444
                    return cell;
7445
                }
7446
            }
7447
            return null;
7448
        },
7449
7450
        removeEmptyTable: function() {
7451
            var cells = this.table.querySelectorAll('td, th');
7452
            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...
7453
                removeElement(this.table);
7454
                return true;
7455
            } 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...
7456
                return false;
7457
            }
7458
        },
7459
7460
        // Splits merged cell on row to unique cells
7461
        splitRowToCells: function(cell) {
7462
            if (cell.isColspan) {
7463
                var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10),
7464
                    cType = cell.el.tagName.toLowerCase();
7465
                if (colspan > 1) {
7466
                    var newCells = this.createCells(cType, colspan -1);
7467
                    insertAfter(cell.el, newCells);
7468
                }
7469
                cell.el.removeAttribute('colspan');
7470
            }
7471
        },
7472
7473
        getRealRowEl: function(force, idx) {
7474
            var r = null,
7475
                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...
7476
7477
            idx = idx || this.idx;
7478
7479
            for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) {
7480
                c = this.map[idx.row][cidx];
7481
                if (c.isReal) {
7482
                    r = api.getParentElement(c.el, { nodeName: ["TR"] });
7483
                    if (r) {
7484
                        return r;
7485
                    }
7486
                }
7487
            }
7488
7489
            if (r === null && force) {
7490
                r = api.getParentElement(this.map[idx.row][idx.col].el, { nodeName: ["TR"] }) || null;
7491
            }
7492
7493
            return r;
7494
        },
7495
7496
        injectRowAt: function(row, col, colspan, cType, c) {
7497
            var r = this.getRealRowEl(false, {'row': row, 'col': col}),
7498
                new_cells = this.createCells(cType, colspan);
7499
7500
            if (r) {
7501
                var n_cidx = this.correctColIndexForUnreals(col, row);
7502
                if (n_cidx >= 0) {
7503
                    insertAfter(this.getRowCells(r)[n_cidx], new_cells);
7504
                } else {
7505
                    r.insertBefore(new_cells, r.firstChild);
7506
                }
7507
            } else {
7508
                var rr = this.table.ownerDocument.createElement('tr');
7509
                rr.appendChild(new_cells);
7510
                insertAfter(api.getParentElement(c.el, { nodeName: ["TR"] }), rr);
7511
            }
7512
        },
7513
7514
        canMerge: function(to) {
7515
            this.to = to;
7516
            this.setTableMap();
7517
            this.idx_start = this.getMapIndex(this.cell);
7518
            this.idx_end = this.getMapIndex(this.to);
7519
7520
            // switch indexes if start is bigger than end
7521
            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)) {
7522
                var temp_idx = this.idx_start;
7523
                this.idx_start = this.idx_end;
7524
                this.idx_end = temp_idx;
7525
            }
7526
            if (this.idx_start.col > this.idx_end.col) {
7527
                var temp_cidx = this.idx_start.col;
7528
                this.idx_start.col = this.idx_end.col;
7529
                this.idx_end.col = temp_cidx;
7530
            }
7531
7532
            for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7533
                for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7534
                    if (this.map[row][col].isColspan || this.map[row][col].isRowspan) {
7535
                        return false;
7536
                    }
7537
                }
7538
            }
7539
            return true;
7540
        },
7541
7542
        decreaseCellSpan: function(cell, span) {
7543
            var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1;
7544
            if (nr >= 1) {
7545
                cell.el.setAttribute(span, nr);
7546
            } else {
7547
                cell.el.removeAttribute(span);
7548
                if (span == 'colspan') {
7549
                    cell.isColspan = false;
7550
                }
7551
                if (span == 'rowspan') {
7552
                    cell.isRowspan = false;
7553
                }
7554
                cell.firstCol = true;
7555
                cell.lastCol = true;
7556
                cell.firstRow = true;
7557
                cell.lastRow = true;
7558
                cell.isReal = true;
7559
            }
7560
        },
7561
7562
        removeSurplusLines: function() {
7563
            var row, cell, ridx, rmax, cidx, cmax, allRowspan;
7564
7565
            this.setTableMap();
7566
            if (this.map) {
7567
                ridx = 0;
7568
                rmax = this.map.length;
7569
                for (;ridx < rmax; ridx++) {
7570
                    row = this.map[ridx];
7571
                    allRowspan = true;
7572
                    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...
7573
                    cmax = row.length;
7574
                    for (; cidx < cmax; cidx++) {
7575
                        cell = row[cidx];
7576
                        if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) {
7577
                            allRowspan = false;
7578
                            break;
7579
                        }
7580
                    }
7581
                    if (allRowspan) {
7582
                        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...
7583
                        for (; cidx < cmax; cidx++) {
7584
                            this.decreaseCellSpan(row[cidx], 'rowspan');
7585
                        }
7586
                    }
7587
                }
7588
7589
                // remove rows without cells
7590
                var tableRows = this.getTableRows();
7591
                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...
7592
                rmax = tableRows.length;
7593
                for (;ridx < rmax; ridx++) {
7594
                    row = tableRows[ridx];
7595
                    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...
7596
                        removeElement(row);
7597
                    }
7598
                }
7599
            }
7600
        },
7601
7602
        fillMissingCells: function() {
7603
            var r_max = 0,
7604
                c_max = 0,
7605
                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...
7606
7607
            this.setTableMap();
7608
            if (this.map) {
7609
7610
                // find maximal dimensions of broken table
7611
                r_max = this.map.length;
7612
                for (var ridx = 0; ridx < r_max; ridx++) {
7613
                    if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; }
7614
                }
7615
7616
                for (var row = 0; row < r_max; row++) {
7617
                    for (var col = 0; col < c_max; col++) {
7618
                        if (this.map[row] && !this.map[row][col]) {
7619
                            if (col > 0) {
7620
                                this.map[row][col] = new MapCell(this.createCells('td', 1));
7621
                                prevcell = this.map[row][col-1];
7622
                                if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom
7623
                                    insertAfter(this.map[row][col-1].el, this.map[row][col].el);
7624
                                }
7625
                            }
7626
                        }
7627
                    }
7628
                }
7629
            }
7630
        },
7631
7632
        rectify: function() {
7633
            if (!this.removeEmptyTable()) {
7634
                this.removeSurplusLines();
7635
                this.fillMissingCells();
7636
                return true;
7637
            } 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...
7638
                return false;
7639
            }
7640
        },
7641
7642
        unmerge: function() {
7643
            if (this.rectify()) {
7644
                this.setTableMap();
7645
                this.idx = this.getMapIndex(this.cell);
7646
7647
                if (this.idx) {
7648
                    var thisCell = this.map[this.idx.row][this.idx.col],
7649
                        colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1,
7650
                        cType = thisCell.el.tagName.toLowerCase();
7651
7652
                    if (thisCell.isRowspan) {
7653
                        var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10);
7654
                        if (rowspan > 1) {
7655
                            for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){
7656
                                this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell);
7657
                            }
7658
                        }
7659
                        thisCell.el.removeAttribute('rowspan');
7660
                    }
7661
                    this.splitRowToCells(thisCell);
7662
                }
7663
            }
7664
        },
7665
7666
        // merges cells from start cell (defined in creating obj) to "to" cell
7667
        merge: function(to) {
7668
            if (this.rectify()) {
7669
                if (this.canMerge(to)) {
7670
                    var rowspan = this.idx_end.row - this.idx_start.row + 1,
7671
                        colspan = this.idx_end.col - this.idx_start.col + 1;
7672
7673
                    for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7674
                        for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7675
7676
                            if (row == this.idx_start.row && col == this.idx_start.col) {
7677
                                if (rowspan > 1) {
7678
                                    this.map[row][col].el.setAttribute('rowspan', rowspan);
7679
                                }
7680
                                if (colspan > 1) {
7681
                                    this.map[row][col].el.setAttribute('colspan', colspan);
7682
                                }
7683
                            } else {
7684
                                // transfer content
7685
                                if (!(/^\s*<br\/?>\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) {
7686
                                    this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML;
7687
                                }
7688
                                removeElement(this.map[row][col].el);
7689
                            }
7690
                        }
7691
                    }
7692
                    this.rectify();
7693
                } else {
7694
                    if (window.console) {
7695
                        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...
7696
                    }
7697
                }
7698
            }
7699
        },
7700
7701
        // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell)
7702
        // Cell is moved to next row (if it is real)
7703
        collapseCellToNextRow: function(cell) {
7704
            var cellIdx = this.getMapIndex(cell.el),
7705
                newRowIdx = cellIdx.row + 1,
7706
                newIdx = {'row': newRowIdx, 'col': cellIdx.col};
7707
7708
            if (newRowIdx < this.map.length) {
7709
7710
                var row = this.getRealRowEl(false, newIdx);
7711
                if (row !== null) {
7712
                    var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row);
7713
                    if (n_cidx >= 0) {
7714
                        insertAfter(this.getRowCells(row)[n_cidx], cell.el);
7715
                    } else {
7716
                        var lastCell = this.getLastNewCellOnRow(row, newRowIdx);
7717
                        if (lastCell !== null) {
7718
                            insertAfter(lastCell, cell.el);
7719
                        } else {
7720
                            row.insertBefore(cell.el, row.firstChild);
7721
                        }
7722
                    }
7723
                    if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7724
                        cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7725
                    } else {
7726
                        cell.el.removeAttribute('rowspan');
7727
                    }
7728
                }
7729
            }
7730
        },
7731
7732
        // Removes a cell when removing a row
7733
        // If is rowspan cell then decreases the rowspan
7734
        // and moves cell to next row if needed (is first cell of rowspan)
7735
        removeRowCell: function(cell) {
7736
            if (cell.isReal) {
7737
               if (cell.isRowspan) {
7738
                   this.collapseCellToNextRow(cell);
7739
               } else {
7740
                   removeElement(cell.el);
7741
               }
7742
            } else {
7743
                if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7744
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7745
                } else {
7746
                    cell.el.removeAttribute('rowspan');
7747
                }
7748
            }
7749
        },
7750
7751
        getRowElementsByCell: function() {
7752
            var cells = [];
7753
            this.setTableMap();
7754
            this.idx = this.getMapIndex(this.cell);
7755
            if (this.idx !== false) {
7756
                var modRow = this.map[this.idx.row];
7757
                for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7758
                    if (modRow[cidx].isReal) {
7759
                        cells.push(modRow[cidx].el);
7760
                    }
7761
                }
7762
            }
7763
            return cells;
7764
        },
7765
7766
        getColumnElementsByCell: function() {
7767
            var cells = [];
7768
            this.setTableMap();
7769
            this.idx = this.getMapIndex(this.cell);
7770
            if (this.idx !== false) {
7771
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7772
                    if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) {
7773
                        cells.push(this.map[ridx][this.idx.col].el);
7774
                    }
7775
                }
7776
            }
7777
            return cells;
7778
        },
7779
7780
        // Removes the row of selected cell
7781
        removeRow: function() {
7782
            var oldRow = api.getParentElement(this.cell, { nodeName: ["TR"] });
7783
            if (oldRow) {
7784
                this.setTableMap();
7785
                this.idx = this.getMapIndex(this.cell);
7786
                if (this.idx !== false) {
7787
                    var modRow = this.map[this.idx.row];
7788
                    for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7789
                        if (!modRow[cidx].modified) {
7790
                            this.setCellAsModified(modRow[cidx]);
7791
                            this.removeRowCell(modRow[cidx]);
7792
                        }
7793
                    }
7794
                }
7795
                removeElement(oldRow);
7796
            }
7797
        },
7798
7799
        removeColCell: function(cell) {
7800
            if (cell.isColspan) {
7801
                if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) {
7802
                    cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1);
7803
                } else {
7804
                    cell.el.removeAttribute('colspan');
7805
                }
7806
            } else if (cell.isReal) {
7807
                removeElement(cell.el);
7808
            }
7809
        },
7810
7811
        removeColumn: function() {
7812
            this.setTableMap();
7813
            this.idx = this.getMapIndex(this.cell);
7814
            if (this.idx !== false) {
7815
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7816
                    if (!this.map[ridx][this.idx.col].modified) {
7817
                        this.setCellAsModified(this.map[ridx][this.idx.col]);
7818
                        this.removeColCell(this.map[ridx][this.idx.col]);
7819
                    }
7820
                }
7821
            }
7822
        },
7823
7824
        // removes row or column by selected cell element
7825
        remove: function(what) {
7826
            if (this.rectify()) {
7827
                switch (what) {
7828
                    case 'row':
7829
                        this.removeRow();
7830
                    break;
7831
                    case 'column':
7832
                        this.removeColumn();
7833
                    break;
7834
                }
7835
                this.rectify();
7836
            }
7837
        },
7838
7839
        addRow: function(where) {
7840
            var doc = this.table.ownerDocument;
7841
7842
            this.setTableMap();
7843
            this.idx = this.getMapIndex(this.cell);
7844
            if (where == "below" && api.getAttribute(this.cell, 'rowspan')) {
7845
                this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1;
7846
            }
7847
7848
            if (this.idx !== false) {
7849
                var modRow = this.map[this.idx.row],
7850
                    newRow = doc.createElement('tr');
7851
7852
                for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) {
7853
                    if (!modRow[ridx].modified) {
7854
                        this.setCellAsModified(modRow[ridx]);
7855
                        this.addRowCell(modRow[ridx], newRow, where);
7856
                    }
7857
                }
7858
7859
                switch (where) {
7860
                    case 'below':
7861
                        insertAfter(this.getRealRowEl(true), newRow);
7862
                    break;
7863
                    case 'above':
7864
                        var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { nodeName: ["TR"] });
7865
                        if (cr) {
7866
                            cr.parentNode.insertBefore(newRow, cr);
7867
                        }
7868
                    break;
7869
                }
7870
            }
7871
        },
7872
7873
        addRowCell: function(cell, row, where) {
7874
            var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null;
7875
            if (cell.isReal) {
7876
                if (where != 'above' && cell.isRowspan) {
7877
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1);
7878
                } else {
7879
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
7880
                }
7881
            } else {
7882
                if (where != 'above' && cell.isRowspan && cell.lastRow) {
7883
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
7884
                } 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...
7885
                    cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1);
7886
                }
7887
            }
7888
        },
7889
7890
        add: function(where) {
7891
            if (this.rectify()) {
7892
                if (where == 'below' || where == 'above') {
7893
                    this.addRow(where);
7894
                }
7895
                if (where == 'before' || where == 'after') {
7896
                    this.addColumn(where);
7897
                }
7898
            }
7899
        },
7900
7901
        addColCell: function (cell, ridx, where) {
7902
            var doAdd,
7903
                cType = cell.el.tagName.toLowerCase();
7904
7905
            // defines add cell vs expand cell conditions
7906
            // true means add
7907
            switch (where) {
7908
                case "before":
7909
                    doAdd = (!cell.isColspan || cell.firstCol);
7910
                break;
7911
                case "after":
7912
                    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...
7913
                break;
7914
            }
7915
7916
            if (doAdd){
7917
                // adds a cell before or after current cell element
7918
                switch (where) {
7919
                    case "before":
7920
                        cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el);
7921
                    break;
7922
                    case "after":
7923
                        insertAfter(cell.el, this.createCells(cType, 1));
7924
                    break;
7925
                }
7926
7927
                // handles if cell has rowspan
7928
                if (cell.isRowspan) {
7929
                    this.handleCellAddWithRowspan(cell, ridx+1, where);
7930
                }
7931
7932
            } else {
7933
                // expands cell
7934
                cell.el.setAttribute('colspan',  parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1);
7935
            }
7936
        },
7937
7938
        addColumn: function(where) {
7939
            var row, modCell;
7940
7941
            this.setTableMap();
7942
            this.idx = this.getMapIndex(this.cell);
7943
            if (where == "after" && api.getAttribute(this.cell, 'colspan')) {
7944
              this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1;
7945
            }
7946
7947
            if (this.idx !== false) {
7948
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) {
7949
                    row = this.map[ridx];
7950
                    if (row[this.idx.col]) {
7951
                        modCell = row[this.idx.col];
7952
                        if (!modCell.modified) {
7953
                            this.setCellAsModified(modCell);
7954
                            this.addColCell(modCell, ridx , where);
7955
                        }
7956
                    }
7957
                }
7958
            }
7959
        },
7960
7961
        handleCellAddWithRowspan: function (cell, ridx, where) {
7962
            var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1,
7963
                crow = api.getParentElement(cell.el, { nodeName: ["TR"] }),
7964
                cType = cell.el.tagName.toLowerCase(),
7965
                cidx, temp_r_cells,
7966
                doc = this.table.ownerDocument,
7967
                nrow;
7968
7969
            for (var i = 0; i < addRowsNr; i++) {
7970
                cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i));
7971
                crow = nextNode(crow, 'tr');
7972
                if (crow) {
7973
                    if (cidx > 0) {
7974
                        switch (where) {
7975
                            case "before":
7976
                                temp_r_cells = this.getRowCells(crow);
7977
                                if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) {
7978
                                     insertAfter(temp_r_cells[cidx], this.createCells(cType, 1));
7979
                                } else {
7980
                                    temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]);
7981
                                }
7982
7983
                            break;
7984
                            case "after":
7985
                                insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1));
7986
                            break;
7987
                        }
7988
                    } else {
7989
                        crow.insertBefore(this.createCells(cType, 1), crow.firstChild);
7990
                    }
7991
                } else {
7992
                    nrow = doc.createElement('tr');
7993
                    nrow.appendChild(this.createCells(cType, 1));
7994
                    this.table.appendChild(nrow);
7995
                }
7996
            }
7997
        }
7998
    };
7999
8000
    api.table = {
8001
        getCellsBetween: function(cell1, cell2) {
8002
            var c1 = new TableModifyerByCell(cell1);
8003
            return c1.getMapElsTo(cell2);
8004
        },
8005
8006
        addCells: function(cell, where) {
8007
            var c = new TableModifyerByCell(cell);
8008
            c.add(where);
8009
        },
8010
8011
        removeCells: function(cell, what) {
8012
            var c = new TableModifyerByCell(cell);
8013
            c.remove(what);
8014
        },
8015
8016
        mergeCellsBetween: function(cell1, cell2) {
8017
            var c1 = new TableModifyerByCell(cell1);
8018
            c1.merge(cell2);
8019
        },
8020
8021
        unmergeCell: function(cell) {
8022
            var c = new TableModifyerByCell(cell);
8023
            c.unmerge();
8024
        },
8025
8026
        orderSelectionEnds: function(cell, cell2) {
8027
            var c = new TableModifyerByCell(cell);
8028
            return c.orderSelectionEnds(cell2);
8029
        },
8030
8031
        indexOf: function(cell) {
8032
            var c = new TableModifyerByCell(cell);
8033
            c.setTableMap();
8034
            return c.getMapIndex(cell);
8035
        },
8036
8037
        findCell: function(table, idx) {
8038
            var c = new TableModifyerByCell(null, table);
8039
            return c.getElementAtIndex(idx);
8040
        },
8041
8042
        findRowByCell: function(cell) {
8043
            var c = new TableModifyerByCell(cell);
8044
            return c.getRowElementsByCell();
8045
        },
8046
8047
        findColumnByCell: function(cell) {
8048
            var c = new TableModifyerByCell(cell);
8049
            return c.getColumnElementsByCell();
8050
        },
8051
8052
        canMerge: function(cell1, cell2) {
8053
            var c = new TableModifyerByCell(cell1);
8054
            return c.canMerge(cell2);
8055
        }
8056
    };
8057
8058
8059
8060
})(wysihtml5);
8061
;// does a selector query on element or array of elements
8062
8063
wysihtml5.dom.query = function(elements, query) {
8064
    var ret = [],
8065
        q;
8066
8067
    if (elements.nodeType) {
8068
        elements = [elements];
8069
    }
8070
8071
    for (var e = 0, len = elements.length; e < len; e++) {
8072
        q = elements[e].querySelectorAll(query);
8073
        if (q) {
8074
            for(var i = q.length; i--; ret.unshift(q[i]));
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...
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...
8075
        }
8076
    }
8077
    return ret;
8078
};
8079
;wysihtml5.dom.compareDocumentPosition = (function() {
8080
  var documentElement = document.documentElement;
8081
  if (documentElement.compareDocumentPosition) {
8082
    return function(container, element) {
8083
      return container.compareDocumentPosition(element);
8084
    };
8085
  } 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...
8086
    return function( container, element ) {
8087
      // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
8088
      var thisOwner, otherOwner;
8089
8090
      if( container.nodeType === 9) // Node.DOCUMENT_NODE
8091
        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...
8092
      else
8093
        thisOwner = container.ownerDocument;
8094
8095
      if( element.nodeType === 9) // Node.DOCUMENT_NODE
8096
        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...
8097
      else
8098
        otherOwner = element.ownerDocument;
8099
8100
      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...
8101
      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...
8102
      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...
8103
      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...
8104
8105
      // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
8106
      if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml5.lang.array(container.childNodes).indexOf( element ) !== -1)
8107
        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...
8108
8109
      if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml5.lang.array(element.childNodes).indexOf( container ) !== -1)
8110
        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...
8111
8112
      var point = container;
8113
      var parents = [ ];
8114
      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...
8115
      while( point ) {
8116
        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...
8117
        parents.push( point );
8118
        point = point.parentNode;
8119
      }
8120
      point = element;
8121
      previous = null;
8122
      while( point ) {
8123
        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...
8124
        var location_index = wysihtml5.lang.array(parents).indexOf( point );
8125
        if( location_index !== -1) {
8126
         var smallest_common_ancestor = parents[ location_index ];
8127
         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] );
8128
         var other_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
8129
         if( this_index > other_index ) {
8130
               return 2; //Node.DOCUMENT_POSITION_PRECEDING;
8131
         }
8132
         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...
8133
           return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
8134
         }
8135
        }
8136
        previous = point;
8137
        point = point.parentNode;
8138
      }
8139
      return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
8140
    };
8141
  }
8142
})();
8143
;wysihtml5.dom.unwrap = function(node) {
8144
  if (node.parentNode) {
8145
    while (node.lastChild) {
8146
      wysihtml5.dom.insert(node.lastChild).after(node);
8147
    }
8148
    node.parentNode.removeChild(node);
8149
  }
8150
};;/**
8151
 * Fix most common html formatting misbehaviors of browsers implementation when inserting
8152
 * content via copy & paste contentEditable
8153
 *
8154
 * @author Christopher Blum
8155
 */
8156
wysihtml5.quirks.cleanPastedHTML = (function() {
8157
  // TODO: We probably need more rules here
8158
  var defaultRules = {
8159
    // When pasting underlined links <a> into a contentEditable, IE thinks, it has to insert <u> to keep the styling
8160
    "a u": wysihtml5.dom.replaceWithChildNodes
8161
  };
8162
8163
  function cleanPastedHTML(elementOrHtml, rules, context) {
8164
    rules   = rules || defaultRules;
8165
    context = context || elementOrHtml.ownerDocument || document;
8166
8167
    var element,
8168
        isString = typeof(elementOrHtml) === "string",
8169
        method,
8170
        matches,
8171
        matchesLength,
8172
        i,
8173
        j = 0, n;
8174
    if (isString) {
8175
      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
8176
    } else {
8177
      element = elementOrHtml;
8178
    }
8179
8180
    for (i in rules) {
8181
      matches       = element.querySelectorAll(i);
8182
      method        = rules[i];
8183
      matchesLength = matches.length;
8184
      for (; j<matchesLength; j++) {
8185
        method(matches[j]);
8186
      }
8187
    }
8188
8189
    // replace joined non-breakable spaces with unjoined
8190
    var txtnodes = wysihtml5.dom.getTextNodes(element);
8191
    for (n = txtnodes.length; n--;) {
8192
      txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
8193
    }
8194
8195
    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...
8196
8197
    return isString ? element.innerHTML : element;
8198
  }
8199
8200
  return cleanPastedHTML;
8201
})();
8202
;/**
8203
 * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
8204
 *
8205
 * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
8206
 * @exaple
8207
 *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
8208
 */
8209
wysihtml5.quirks.ensureProperClearing = (function() {
8210
  var clearIfNecessary = function() {
8211
    var element = this;
8212
    setTimeout(function() {
8213
      var innerHTML = element.innerHTML.toLowerCase();
8214
      if (innerHTML == "<p>&nbsp;</p>" ||
8215
          innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
8216
        element.innerHTML = "";
8217
      }
8218
    }, 0);
8219
  };
8220
8221
  return function(composer) {
8222
    wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
8223
  };
8224
})();
8225
;// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
8226
//
8227
// In Firefox this:
8228
//      var d = document.createElement("div");
8229
//      d.innerHTML ='<a href="~"></a>';
8230
//      d.innerHTML;
8231
// will result in:
8232
//      <a href="%7E"></a>
8233
// which is wrong
8234
(function(wysihtml5) {
8235
  var TILDE_ESCAPED = "%7E";
8236
  wysihtml5.quirks.getCorrectInnerHTML = function(element) {
8237
    var innerHTML = element.innerHTML;
8238
    if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
8239
      return innerHTML;
8240
    }
8241
8242
    var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
8243
        url,
8244
        urlToSearch,
8245
        length,
8246
        i;
8247
    for (i=0, length=elementsWithTilde.length; i<length; i++) {
8248
      url         = elementsWithTilde[i].href || elementsWithTilde[i].src;
8249
      urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
8250
      innerHTML   = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
8251
    }
8252
    return innerHTML;
8253
  };
8254
})(wysihtml5);
8255
;/**
8256
 * Force rerendering of a given element
8257
 * Needed to fix display misbehaviors of IE
8258
 *
8259
 * @param {Element} element The element object which needs to be rerendered
8260
 * @example
8261
 *    wysihtml5.quirks.redraw(document.body);
8262
 */
8263
(function(wysihtml5) {
8264
  var CLASS_NAME = "wysihtml5-quirks-redraw";
8265
8266
  wysihtml5.quirks.redraw = function(element) {
8267
    wysihtml5.dom.addClass(element, CLASS_NAME);
8268
    wysihtml5.dom.removeClass(element, CLASS_NAME);
8269
8270
    // Following hack is needed for firefox to make sure that image resize handles are properly removed
8271
    try {
8272
      var doc = element.ownerDocument;
8273
      doc.execCommand("italic", false, null);
8274
      doc.execCommand("italic", false, null);
8275
    } 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...
8276
  };
8277
})(wysihtml5);
8278
;wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
8279
8280
    var dom = wysihtml5.dom,
8281
        select = {
8282
            table: null,
8283
            start: null,
8284
            end: null,
8285
            cells: null,
8286
            select: selectCells
8287
        },
8288
        selection_class = "wysiwyg-tmp-selected-cell",
8289
        moveHandler = null,
8290
        upHandler = null;
8291
8292
    function init () {
8293
8294
        dom.observe(editable, "mousedown", function(event) {
8295
          var target = wysihtml5.dom.getParentElement(event.target, { nodeName: ["TD", "TH"] });
8296
          if (target) {
8297
              handleSelectionMousedown(target);
8298
          }
8299
        });
8300
8301
        return select;
8302
    }
8303
8304
    function handleSelectionMousedown (target) {
8305
      select.start = target;
8306
      select.end = target;
8307
      select.cells = [target];
8308
      select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8309
8310
      if (select.table) {
8311
        removeCellSelections();
8312
        dom.addClass(target, selection_class);
8313
        moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
8314
        upHandler = dom.observe(editable, "mouseup", handleMouseUp);
8315
        editor.fire("tableselectstart").fire("tableselectstart:composer");
8316
      }
8317
    }
8318
8319
    // remove all selection classes
8320
    function removeCellSelections () {
8321
        if (editable) {
8322
            var selectedCells = editable.querySelectorAll('.' + selection_class);
8323
            if (selectedCells.length > 0) {
8324
              for (var i = 0; i < selectedCells.length; i++) {
8325
                  dom.removeClass(selectedCells[i], selection_class);
8326
              }
8327
            }
8328
        }
8329
    }
8330
8331
    function addSelections (cells) {
8332
      for (var i = 0; i < cells.length; i++) {
8333
        dom.addClass(cells[i], selection_class);
8334
      }
8335
    }
8336
8337
    function handleMouseMove (event) {
8338
      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...
8339
          cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
8340
          oldEnd;
8341
8342
      if (cell && select.table && select.start) {
8343
        curTable =  dom.getParentElement(cell, { nodeName: ["TABLE"] });
8344
        if (curTable && curTable === select.table) {
8345
          removeCellSelections();
8346
          oldEnd = select.end;
8347
          select.end = cell;
8348
          select.cells = dom.table.getCellsBetween(select.start, cell);
8349
          if (select.cells.length > 1) {
8350
            editor.composer.selection.deselect();
8351
          }
8352
          addSelections(select.cells);
8353
          if (select.end !== oldEnd) {
8354
            editor.fire("tableselectchange").fire("tableselectchange:composer");
8355
          }
8356
        }
8357
      }
8358
    }
8359
8360
    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...
8361
      moveHandler.stop();
8362
      upHandler.stop();
8363
      editor.fire("tableselect").fire("tableselect:composer");
8364
      setTimeout(function() {
8365
        bindSideclick();
8366
      },0);
8367
    }
8368
8369
    function bindSideclick () {
8370
        var sideClickHandler = dom.observe(editable.ownerDocument, "click", function(event) {
8371
          sideClickHandler.stop();
8372
          if (dom.getParentElement(event.target, { nodeName: ["TABLE"] }) != select.table) {
8373
              removeCellSelections();
8374
              select.table = null;
8375
              select.start = null;
8376
              select.end = null;
8377
              editor.fire("tableunselect").fire("tableunselect:composer");
8378
          }
8379
        });
8380
    }
8381
8382
    function selectCells (start, end) {
8383
        select.start = start;
8384
        select.end = end;
8385
        select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8386
        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...
8387
        addSelections(selectedCells);
8388
        bindSideclick();
8389
        editor.fire("tableselect").fire("tableselect:composer");
8390
    }
8391
8392
    return init();
8393
8394
};
8395
;(function(wysihtml5) {
8396
  var RGBA_REGEX     = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
8397
      RGB_REGEX      = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
8398
      HEX6_REGEX     = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
8399
      HEX3_REGEX     = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
8400
8401
  var param_REGX = function (p) {
8402
    return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+" , "gi");
8403
  };
8404
8405
  wysihtml5.quirks.styleParser = {
8406
8407
    parseColor: function(stylesStr, paramName) {
8408
      var paramRegex = param_REGX(paramName),
8409
          params = stylesStr.match(paramRegex),
8410
          radix = 10,
8411
          str, colorMatch;
8412
8413
      if (params) {
8414
        for (var i = params.length; i--;) {
8415
          params[i] = wysihtml5.lang.string(params[i].split(':')[1]).trim();
8416
        }
8417
        str = params[params.length-1];
8418
8419
        if (RGBA_REGEX.test(str)) {
8420
          colorMatch = str.match(RGBA_REGEX);
8421
        } else if (RGB_REGEX.test(str)) {
8422
          colorMatch = str.match(RGB_REGEX);
8423
        } else if (HEX6_REGEX.test(str)) {
8424
          colorMatch = str.match(HEX6_REGEX);
8425
          radix = 16;
8426
        } else if (HEX3_REGEX.test(str)) {
8427
          colorMatch = str.match(HEX3_REGEX);
8428
          colorMatch.shift();
8429
          colorMatch.push(1);
8430
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8431
            return (idx < 3) ? (parseInt(d, 16) * 16) + parseInt(d, 16): parseFloat(d);
8432
          });
8433
        }
8434
8435
        if (colorMatch) {
8436
          colorMatch.shift();
8437
          if (!colorMatch[3]) {
8438
            colorMatch.push(1);
8439
          }
8440
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8441
            return (idx < 3) ? parseInt(d, radix): parseFloat(d);
8442
          });
8443
        }
8444
      }
8445
      return false;
8446
    },
8447
8448
    unparseColor: function(val, props) {
8449
      if (props) {
8450
        if (props == "hex") {
8451
          return (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8452
        } else if (props == "hash") {
8453
          return "#" + (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8454
        } else if (props == "rgb") {
8455
          return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8456
        } else if (props == "rgba") {
8457
          return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8458
        } else if (props == "csv") {
8459
          return  val[0] + "," + val[1] + "," + val[2] + "," + val[3];
8460
        }
8461
      }
8462
8463
      if (val[3] && val[3] !== 1) {
8464
        return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8465
      } 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...
8466
        return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8467
      }
8468
    },
8469
8470
    parseFontSize: function(stylesStr) {
8471
      var params = stylesStr.match(param_REGX('font-size'));
8472
      if (params) {
8473
        return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
8474
      }
8475
      return false;
8476
    }
8477
  };
8478
8479
})(wysihtml5);
8480
;/**
8481
 * Selection API
8482
 *
8483
 * @example
8484
 *    var selection = new wysihtml5.Selection(editor);
8485
 */
8486
(function(wysihtml5) {
8487
  var dom = wysihtml5.dom;
8488
8489
  function _getCumulativeOffsetTop(element) {
8490
    var top = 0;
8491
    if (element.parentNode) {
8492
      do {
8493
        top += element.offsetTop || 0;
8494
        element = element.offsetParent;
8495
      } while (element);
8496
    }
8497
    return top;
8498
  }
8499
8500
  // Provides the depth of ``descendant`` relative to ``ancestor``
8501
  function getDepth(ancestor, descendant) {
8502
      var ret = 0;
8503
      while (descendant !== ancestor) {
8504
          ret++;
8505
          descendant = descendant.parentNode;
8506
          if (!descendant)
8507
              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...
8508
      }
8509
      return ret;
8510
  }
8511
8512
  // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
8513
  // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
8514
  function expandRangeToSurround(range) {
8515
      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...
8516
8517
      var common = range.commonAncestorContainer,
8518
          start_depth = getDepth(common, range.startContainer),
8519
          end_depth = getDepth(common, range.endContainer);
8520
8521
      while(!range.canSurroundContents()) {
8522
        // 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.
8523
        if (start_depth > end_depth) {
8524
            range.setStartBefore(range.startContainer);
8525
            start_depth = getDepth(common, range.startContainer);
8526
        }
8527
        else {
8528
            range.setEndAfter(range.endContainer);
8529
            end_depth = getDepth(common, range.endContainer);
8530
        }
8531
      }
8532
  }
8533
8534
  wysihtml5.Selection = Base.extend(
8535
    /** @scope wysihtml5.Selection.prototype */ {
8536
    constructor: function(editor, contain, unselectableClass) {
8537
      // Make sure that our external range library is initialized
8538
      window.rangy.init();
8539
8540
      this.editor   = editor;
8541
      this.composer = editor.composer;
8542
      this.doc      = this.composer.doc;
8543
      this.contain = contain;
8544
      this.unselectableClass = unselectableClass || false;
8545
    },
8546
8547
    /**
8548
     * Get the current selection as a bookmark to be able to later restore it
8549
     *
8550
     * @return {Object} An object that represents the current selection
8551
     */
8552
    getBookmark: function() {
8553
      var range = this.getRange();
8554
      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...
8555
      return range && range.cloneRange();
8556
    },
8557
8558
    /**
8559
     * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
8560
     *
8561
     * @param {Object} bookmark An object that represents the current selection
8562
     */
8563
    setBookmark: function(bookmark) {
8564
      if (!bookmark) {
8565
        return;
8566
      }
8567
8568
      this.setSelection(bookmark);
8569
    },
8570
8571
    /**
8572
     * Set the caret in front of the given node
8573
     *
8574
     * @param {Object} node The element or text node where to position the caret in front of
8575
     * @example
8576
     *    selection.setBefore(myElement);
8577
     */
8578
    setBefore: function(node) {
8579
      var range = rangy.createRange(this.doc);
8580
      range.setStartBefore(node);
8581
      range.setEndBefore(node);
8582
      return this.setSelection(range);
8583
    },
8584
8585
    /**
8586
     * Set the caret after the given node
8587
     *
8588
     * @param {Object} node The element or text node where to position the caret in front of
8589
     * @example
8590
     *    selection.setBefore(myElement);
8591
     */
8592
    setAfter: function(node) {
8593
      var range = rangy.createRange(this.doc);
8594
8595
      range.setStartAfter(node);
8596
      range.setEndAfter(node);
8597
      return this.setSelection(range);
8598
    },
8599
8600
    /**
8601
     * Ability to select/mark nodes
8602
     *
8603
     * @param {Element} node The node/element to select
8604
     * @example
8605
     *    selection.selectNode(document.getElementById("my-image"));
8606
     */
8607
    selectNode: function(node, avoidInvisibleSpace) {
8608
      var range           = rangy.createRange(this.doc),
8609
          isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
8610
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
8611
          content         = isElement ? node.innerHTML : node.data,
8612
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
8613
          displayStyle    = dom.getStyle("display").from(node),
8614
          isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
8615
8616
      if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
8617
        // Make sure that caret is visible in node by inserting a zero width no breaking space
8618
        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...
8619
      }
8620
8621
      if (canHaveHTML) {
8622
        range.selectNodeContents(node);
8623
      } else {
8624
        range.selectNode(node);
8625
      }
8626
8627
      if (canHaveHTML && isEmpty && isElement) {
8628
        range.collapse(isBlockElement);
8629
      } else if (canHaveHTML && isEmpty) {
8630
        range.setStartAfter(node);
8631
        range.setEndAfter(node);
8632
      }
8633
8634
      this.setSelection(range);
8635
    },
8636
8637
    /**
8638
     * Get the node which contains the selection
8639
     *
8640
     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
8641
     * @return {Object} The node that contains the caret
8642
     * @example
8643
     *    var nodeThatContainsCaret = selection.getSelectedNode();
8644
     */
8645
    getSelectedNode: function(controlRange) {
8646
      var selection,
8647
          range;
8648
8649
      if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
8650
        range = this.doc.selection.createRange();
8651
        if (range && range.length) {
8652
          return range.item(0);
8653
        }
8654
      }
8655
8656
      selection = this.getSelection(this.doc);
8657
      if (selection.focusNode === selection.anchorNode) {
8658
        return selection.focusNode;
8659
      } 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...
8660
        range = this.getRange(this.doc);
8661
        return range ? range.commonAncestorContainer : this.doc.body;
8662
      }
8663
    },
8664
8665
    fixSelBorders: function() {
8666
      var range = this.getRange();
8667
      expandRangeToSurround(range);
8668
      this.setSelection(range);
8669
    },
8670
8671
    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...
8672
      var selection,
0 ignored issues
show
Unused Code introduced by
The variable selection seems to be never used. Consider removing it.
Loading history...
8673
          ranges = this.getOwnRanges(),
8674
          ownNodes = [];
8675
8676
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8677
          ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
8678
      }
8679
      return ownNodes;
8680
    },
8681
8682
    findNodesInSelection: function(nodeTypes) {
8683
      var ranges = this.getOwnRanges(),
8684
          nodes = [], curNodes;
8685
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8686
        curNodes = ranges[i].getNodes([1], function(node) {
8687
            return wysihtml5.lang.array(nodeTypes).contains(node.nodeName);
8688
        });
8689
        nodes = nodes.concat(curNodes);
8690
      }
8691
      return nodes;
8692
    },
8693
8694
    containsUneditable: function() {
8695
      var uneditables = this.getOwnUneditables(),
8696
          selection = this.getSelection();
8697
8698
      for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
8699
        if (selection.containsNode(uneditables[i])) {
8700
          return true;
8701
        }
8702
      }
8703
8704
      return false;
8705
    },
8706
8707
    deleteContents: function()  {
8708
      var ranges = this.getOwnRanges();
8709
      for (var i = ranges.length; i--;) {
8710
        ranges[i].deleteContents();
8711
      }
8712
      this.setSelection(ranges[0]);
8713
    },
8714
8715
    getPreviousNode: function(node, ignoreEmpty) {
8716
      if (!node) {
8717
        var selection = this.getSelection();
8718
        node = selection.anchorNode;
8719
      }
8720
8721
      if (node === this.contain) {
8722
          return false;
8723
      }
8724
8725
      var ret = node.previousSibling,
8726
          parent;
8727
8728
      if (ret === this.contain) {
8729
          return false;
8730
      }
8731
8732
      if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
8733
         // do not count comments and other node types
8734
         ret = this.getPreviousNode(ret, ignoreEmpty);
8735
      } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
8736
        // do not count empty textnodes as previus nodes
8737
        ret = this.getPreviousNode(ret, ignoreEmpty);
8738
      } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML)) {
8739
        // Do not count empty nodes if param set.
8740
        // Contenteditable tends to bypass and delete these silently when deleting with caret
8741
        ret = this.getPreviousNode(ret, ignoreEmpty);
8742
      } else if (!ret && node !== this.contain) {
8743
        parent = node.parentNode;
8744
        if (parent !== this.contain) {
8745
            ret = this.getPreviousNode(parent, ignoreEmpty);
8746
        }
8747
      }
8748
8749
      return (ret !== this.contain) ? ret : false;
8750
    },
8751
8752
    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...
8753
      var nodes = this.getSelectedOwnNodes(),
8754
          curEl, parents = [];
8755
8756
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
8757
        curEl = (nodes[i].nodeName &&  nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml5.dom.getParentElement(nodes[i], { nodeName: ['LI']}, false, this.contain);
8758
        if (curEl) {
8759
          parents.push(curEl);
8760
        }
8761
      }
8762
      return (parents.length) ? parents : null;
8763
    },
8764
8765
    getRangeToNodeEnd: function() {
8766
      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...
8767
        var range = this.getRange(),
8768
            sNode = range.startContainer,
8769
            pos = range.startOffset,
8770
            lastR = rangy.createRange(this.doc);
8771
8772
        lastR.selectNodeContents(sNode);
8773
        lastR.setStart(sNode, pos);
8774
        return lastR;
8775
      }
8776
    },
8777
8778
    caretIsLastInSelection: function() {
8779
      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...
8780
          s = this.getSelection(),
0 ignored issues
show
Unused Code introduced by
The variable s seems to be never used. Consider removing it.
Loading history...
8781
          endc = this.getRangeToNodeEnd().cloneContents(),
8782
          endtxt = endc.textContent;
8783
8784
      return (/^\s*$/).test(endtxt);
8785
    },
8786
8787
    caretIsFirstInSelection: function() {
8788
      var r = rangy.createRange(this.doc),
8789
          s = this.getSelection(),
8790
          range = this.getRange(),
8791
          startNode = range.startContainer;
8792
      
8793
      if (startNode.nodeType === wysihtml5.TEXT_NODE) {
8794
        return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
8795
      } 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...
8796
        r.selectNodeContents(this.getRange().commonAncestorContainer);
8797
        r.collapse(true);
8798
        return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
8799
      }
8800
    },
8801
8802
    caretIsInTheBeginnig: function(ofNode) {
8803
        var selection = this.getSelection(),
8804
            node = selection.anchorNode,
8805
            offset = selection.anchorOffset;
8806
        if (ofNode) {
8807
          return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
8808
        } 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...
8809
          return (offset === 0 && !this.getPreviousNode(node, true));
8810
        }
8811
    },
8812
8813
    caretIsBeforeUneditable: function() {
8814
      var selection = this.getSelection(),
8815
          node = selection.anchorNode,
8816
          offset = selection.anchorOffset;
8817
8818
      if (offset === 0) {
8819
        var prevNode = this.getPreviousNode(node, true);
8820
        if (prevNode) {
8821
          var uneditables = this.getOwnUneditables();
8822
          for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
8823
            if (prevNode === uneditables[i]) {
8824
              return uneditables[i];
8825
            }
8826
          }
8827
        }
8828
      }
8829
      return false;
8830
    },
8831
8832
    // TODO: Figure out a method from following 3 that would work universally
8833
    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...
8834
      var win = this.doc.defaultView || this.doc.parentWindow,
8835
          sel = rangy.saveSelection(win);
8836
8837
      if (!sel) {
8838
        method();
8839
      } else {
8840
        try {
8841
          method();
8842
        } catch(e) {
8843
          setTimeout(function() { throw e; }, 0);
8844
        }
8845
      }
8846
      rangy.restoreSelection(sel);
8847
    },
8848
8849
    // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
8850
    executeAndRestore: function(method, restoreScrollPosition) {
8851
      var body                  = this.doc.body,
8852
          oldScrollTop          = restoreScrollPosition && body.scrollTop,
8853
          oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
8854
          className             = "_wysihtml5-temp-placeholder",
8855
          placeholderHtml       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
8856
          range                 = this.getRange(true),
8857
          caretPlaceholder,
8858
          newCaretPlaceholder,
8859
          nextSibling, prevSibling,
8860
          node, node2, range2,
8861
          newRange;
8862
8863
      // Nothing selected, execute and say goodbye
8864
      if (!range) {
8865
        method(body, body);
8866
        return;
8867
      }
8868
8869
      if (!range.collapsed) {
8870
        range2 = range.cloneRange();
8871
        node2 = range2.createContextualFragment(placeholderHtml);
8872
        range2.collapse(false);
8873
        range2.insertNode(node2);
8874
        range2.detach();
8875
      }
8876
8877
      node = range.createContextualFragment(placeholderHtml);
8878
      range.insertNode(node);
8879
8880
      if (node2) {
8881
        caretPlaceholder = this.contain.querySelectorAll("." + className);
8882
        range.setStartBefore(caretPlaceholder[0]);
8883
        range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
8884
      }
8885
      this.setSelection(range);
8886
8887
      // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
8888
      try {
8889
        method(range.startContainer, range.endContainer);
8890
      } catch(e) {
8891
        setTimeout(function() { throw e; }, 0);
8892
      }
8893
      caretPlaceholder = this.contain.querySelectorAll("." + className);
8894
      if (caretPlaceholder && caretPlaceholder.length) {
8895
        newRange = rangy.createRange(this.doc);
8896
        nextSibling = caretPlaceholder[0].nextSibling;
8897
        if (caretPlaceholder.length > 1) {
8898
          prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
8899
        }
8900
        if (prevSibling && nextSibling) {
8901
          newRange.setStartBefore(nextSibling);
8902
          newRange.setEndAfter(prevSibling);
8903
        } else {
8904
          newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
8905
          dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
8906
          newRange.setStartBefore(newCaretPlaceholder);
8907
          newRange.setEndAfter(newCaretPlaceholder);
8908
        }
8909
        this.setSelection(newRange);
8910
        for (var i = caretPlaceholder.length; i--;) {
8911
         caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
8912
        }
8913
8914
      } else {
8915
        // fallback for when all hell breaks loose
8916
        this.contain.focus();
8917
      }
8918
8919
      if (restoreScrollPosition) {
8920
        body.scrollTop  = oldScrollTop;
8921
        body.scrollLeft = oldScrollLeft;
8922
      }
8923
8924
      // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
8925
      try {
8926
        caretPlaceholder.parentNode.removeChild(caretPlaceholder);
8927
      } 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...
8928
    },
8929
8930
    set: function(node, offset) {
8931
      var newRange = rangy.createRange(this.doc);
8932
      newRange.setStart(node, offset || 0);
8933
      this.setSelection(newRange);
8934
    },
8935
8936
    /**
8937
     * Insert html at the caret position and move the cursor after the inserted html
8938
     *
8939
     * @param {String} html HTML string to insert
8940
     * @example
8941
     *    selection.insertHTML("<p>foobar</p>");
8942
     */
8943
    insertHTML: function(html) {
8944
      var range     = rangy.createRange(this.doc),
8945
          node      = range.createContextualFragment(html),
8946
          lastChild = node.lastChild;
8947
8948
      this.insertNode(node);
8949
      if (lastChild) {
8950
        this.setAfter(lastChild);
8951
      }
8952
    },
8953
8954
    /**
8955
     * Insert a node at the caret position and move the cursor behind it
8956
     *
8957
     * @param {Object} node HTML string to insert
8958
     * @example
8959
     *    selection.insertNode(document.createTextNode("foobar"));
8960
     */
8961
    insertNode: function(node) {
8962
      var range = this.getRange();
8963
      if (range) {
8964
        range.insertNode(node);
8965
      }
8966
    },
8967
8968
    /**
8969
     * Wraps current selection with the given node
8970
     *
8971
     * @param {Object} node The node to surround the selected elements with
8972
     */
8973
    surround: function(nodeOptions) {
8974
      var ranges = this.getOwnRanges(),
8975
          node, nodes = [];
8976
      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...
8977
        return nodes;
8978
      }
8979
8980
      for (var i = ranges.length; i--;) {
8981
        node = this.doc.createElement(nodeOptions.nodeName);
8982
        nodes.push(node);
8983
        if (nodeOptions.className) {
8984
          node.className = nodeOptions.className;
8985
        }
8986
        if (nodeOptions.cssStyle) {
8987
          node.setAttribute('style', nodeOptions.cssStyle);
8988
        }
8989
        try {
8990
          // This only works when the range boundaries are not overlapping other elements
8991
          ranges[i].surroundContents(node);
8992
          this.selectNode(node);
8993
        } catch(e) {
8994
          // fallback
8995
          node.appendChild(ranges[i].extractContents());
8996
          ranges[i].insertNode(node);
8997
        }
8998
      }
8999
      return nodes;
9000
    },
9001
9002
    deblockAndSurround: function(nodeOptions) {
9003
      var tempElement = this.doc.createElement('div'),
9004
          range = rangy.createRange(this.doc),
9005
          tempDivElements,
9006
          tempElements,
9007
          firstChild;
9008
9009
      tempElement.className = nodeOptions.className;
9010
9011
      this.composer.commands.exec("formatBlock", nodeOptions.nodeName, nodeOptions.className);
9012
      tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
9013
      if (tempDivElements[0]) {
9014
        tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
9015
9016
        range.setStartBefore(tempDivElements[0]);
9017
        range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
9018
        tempElements = range.extractContents();
9019
9020
        while (tempElements.firstChild) {
9021
          firstChild = tempElements.firstChild;
9022
          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...
9023
            while (firstChild.firstChild) {
9024
              tempElement.appendChild(firstChild.firstChild);
9025
            }
9026
            if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
9027
            tempElements.removeChild(firstChild);
9028
          } else {
9029
            tempElement.appendChild(firstChild);
9030
          }
9031
        }
9032
      } else {
9033
        tempElement = null;
9034
      }
9035
9036
      return tempElement;
9037
    },
9038
9039
    /**
9040
     * Scroll the current caret position into the view
9041
     * FIXME: This is a bit hacky, there might be a smarter way of doing this
9042
     *
9043
     * @example
9044
     *    selection.scrollIntoView();
9045
     */
9046
    scrollIntoView: function() {
9047
      var doc           = this.doc,
9048
          tolerance     = 5, // px
9049
          hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
9050
          tempElement   = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
9051
            var element = doc.createElement("span");
9052
            // The element needs content in order to be able to calculate it's position properly
9053
            element.innerHTML = wysihtml5.INVISIBLE_SPACE;
9054
            return element;
9055
          })(),
9056
          offsetTop;
9057
9058
      if (hasScrollBars) {
9059
        this.insertNode(tempElement);
9060
        offsetTop = _getCumulativeOffsetTop(tempElement);
9061
        tempElement.parentNode.removeChild(tempElement);
9062
        if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
9063
          doc.body.scrollTop = offsetTop;
9064
        }
9065
      }
9066
    },
9067
9068
    /**
9069
     * Select line where the caret is in
9070
     */
9071
    selectLine: function() {
9072
      if (wysihtml5.browser.supportsSelectionModify()) {
9073
        this._selectLine_W3C();
9074
      } else if (this.doc.selection) {
9075
        this._selectLine_MSIE();
9076
      }
9077
    },
9078
9079
    /**
9080
     * See https://developer.mozilla.org/en/DOM/Selection/modify
9081
     */
9082
    _selectLine_W3C: function() {
9083
      var win       = this.doc.defaultView,
9084
          selection = win.getSelection();
9085
      selection.modify("move", "left", "lineboundary");
9086
      selection.modify("extend", "right", "lineboundary");
9087
    },
9088
9089
    _selectLine_MSIE: function() {
9090
      var range       = this.doc.selection.createRange(),
9091
          rangeTop    = range.boundingTop,
9092
          scrollWidth = this.doc.body.scrollWidth,
9093
          rangeBottom,
9094
          rangeEnd,
9095
          measureNode,
9096
          i,
9097
          j;
9098
9099
      if (!range.moveToPoint) {
9100
        return;
9101
      }
9102
9103
      if (rangeTop === 0) {
9104
        // Don't know why, but when the selection ends at the end of a line
9105
        // range.boundingTop is 0
9106
        measureNode = this.doc.createElement("span");
9107
        this.insertNode(measureNode);
9108
        rangeTop = measureNode.offsetTop;
9109
        measureNode.parentNode.removeChild(measureNode);
9110
      }
9111
9112
      rangeTop += 1;
9113
9114
      for (i=-10; i<scrollWidth; i+=2) {
9115
        try {
9116
          range.moveToPoint(i, rangeTop);
9117
          break;
9118
        } 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...
9119
      }
9120
9121
      // Investigate the following in order to handle multi line selections
9122
      // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
9123
      rangeBottom = rangeTop;
9124
      rangeEnd = this.doc.selection.createRange();
9125
      for (j=scrollWidth; j>=0; j--) {
9126
        try {
9127
          rangeEnd.moveToPoint(j, rangeBottom);
9128
          break;
9129
        } 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...
9130
      }
9131
9132
      range.setEndPoint("EndToEnd", rangeEnd);
9133
      range.select();
9134
    },
9135
9136
    getText: function() {
9137
      var selection = this.getSelection();
9138
      return selection ? selection.toString() : "";
9139
    },
9140
9141
    getNodes: function(nodeType, filter) {
9142
      var range = this.getRange();
9143
      if (range) {
9144
        return range.getNodes([nodeType], filter);
9145
      } 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...
9146
        return [];
9147
      }
9148
    },
9149
9150
    fixRangeOverflow: function(range) {
9151
      if (this.contain && this.contain.firstChild && range) {
9152
        var containment = range.compareNode(this.contain);
9153
        if (containment !== 2) {
9154
          if (containment === 1) {
9155
            range.setStartBefore(this.contain.firstChild);
9156
          }
9157
          if (containment === 0) {
9158
            range.setEndAfter(this.contain.lastChild);
9159
          }
9160
          if (containment === 3) {
9161
            range.setStartBefore(this.contain.firstChild);
9162
            range.setEndAfter(this.contain.lastChild);
9163
          }
9164
        } else if (this._detectInlineRangeProblems(range)) {
9165
          var previousElementSibling = range.endContainer.previousElementSibling;
9166
          if (previousElementSibling) {
9167
            range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
9168
          }
9169
        }
9170
      }
9171
    },
9172
9173
    _endOffsetForNode: function(node) {
9174
      var range = document.createRange();
9175
      range.selectNodeContents(node);
9176
      return range.endOffset;
9177
    },
9178
9179
    _detectInlineRangeProblems: function(range) {
9180
      var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
9181
      return (
9182
        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...
9183
        position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
9184
      );
9185
    },
9186
9187
    getRange: function(dontFix) {
9188
      var selection = this.getSelection(),
9189
          range = selection && selection.rangeCount && selection.getRangeAt(0);
9190
9191
      if (dontFix !== true) {
9192
        this.fixRangeOverflow(range);
9193
      }
9194
9195
      return range;
9196
    },
9197
9198
    getOwnUneditables: function() {
9199
      var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
9200
          deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
9201
9202
      return wysihtml5.lang.array(allUneditables).without(deepUneditables);
9203
    },
9204
9205
    // Returns an array of ranges that belong only to this editable
9206
    // Needed as uneditable block in contenteditabel can split range into pieces
9207
    // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
9208
    getOwnRanges: function()  {
9209
      var ranges = [],
9210
          r = this.getRange(),
9211
          tmpRanges;
9212
9213
      if (r) { ranges.push(r); }
9214
9215
      if (this.unselectableClass && this.contain && r) {
9216
          var uneditables = this.getOwnUneditables(),
9217
              tmpRange;
9218
          if (uneditables.length > 0) {
9219
            for (var i = 0, imax = uneditables.length; i < imax; i++) {
9220
              tmpRanges = [];
9221
              for (var j = 0, jmax = ranges.length; j < jmax; j++) {
9222
                if (ranges[j]) {
9223
                  switch (ranges[j].compareNode(uneditables[i])) {
9224
                    case 2:
9225
                      // all selection inside uneditable. remove
9226
                    break;
9227
                    case 3:
9228
                      //section begins before and ends after uneditable. spilt
9229
                      tmpRange = ranges[j].cloneRange();
9230
                      tmpRange.setEndBefore(uneditables[i]);
9231
                      tmpRanges.push(tmpRange);
9232
9233
                      tmpRange = ranges[j].cloneRange();
9234
                      tmpRange.setStartAfter(uneditables[i]);
9235
                      tmpRanges.push(tmpRange);
9236
                    break;
9237
                    default:
9238
                      // in all other cases uneditable does not touch selection. dont modify
9239
                      tmpRanges.push(ranges[j]);
9240
                  }
9241
                }
9242
                ranges = tmpRanges;
9243
              }
9244
            }
9245
          }
9246
      }
9247
      return ranges;
9248
    },
9249
9250
    getSelection: function() {
9251
      return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
9252
    },
9253
9254
    setSelection: function(range) {
9255
      var win       = this.doc.defaultView || this.doc.parentWindow,
9256
          selection = rangy.getSelection(win);
9257
      return selection.setSingleRange(range);
9258
    },
9259
9260
    createRange: function() {
9261
      return rangy.createRange(this.doc);
9262
    },
9263
9264
    isCollapsed: function() {
9265
        return this.getSelection().isCollapsed;
9266
    },
9267
9268
    isEndToEndInNode: function(nodeNames) {
9269
      var range = this.getRange(),
9270
          parentElement = range.commonAncestorContainer,
9271
          startNode = range.startContainer,
9272
          endNode = range.endContainer;
9273
9274
9275
        if (parentElement.nodeType === wysihtml5.TEXT_NODE) {
9276
          parentElement = parentElement.parentNode;
9277
        }
9278
9279
        if (startNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
9280
          return false;
9281
        }
9282
9283
        if (endNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
9284
          return false;
9285
        }
9286
9287
        while (startNode && startNode !== parentElement) {
9288
          if (startNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, startNode)) {
9289
            return false;
9290
          }
9291
          if (wysihtml5.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
9292
            return false;
9293
          }
9294
          startNode = startNode.parentNode;
9295
        }
9296
9297
        while (endNode && endNode !== parentElement) {
9298
          if (endNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, endNode)) {
9299
            return false;
9300
          }
9301
          if (wysihtml5.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
9302
            return false;
9303
          }
9304
          endNode = endNode.parentNode;
9305
        }
9306
9307
        return (wysihtml5.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
9308
    },
9309
9310
    deselect: function() {
9311
      var sel = this.getSelection();
9312
      sel && sel.removeAllRanges();
9313
    }
9314
  });
9315
9316
})(wysihtml5);
9317
;/**
9318
 * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
9319
 * http://code.google.com/p/rangy/
9320
 *
9321
 * changed in order to be able ...
9322
 *    - to use custom tags
9323
 *    - to detect and replace similar css classes via reg exp
9324
 */
9325
(function(wysihtml5, rangy) {
9326
  var defaultTagName = "span";
9327
9328
  var REG_EXP_WHITE_SPACE = /\s+/g;
9329
9330
  function hasClass(el, cssClass, regExp) {
9331
    if (!el.className) {
9332
      return false;
9333
    }
9334
9335
    var matchingClassNames = el.className.match(regExp) || [];
9336
    return matchingClassNames[matchingClassNames.length - 1] === cssClass;
9337
  }
9338
9339
  function hasStyleAttr(el, regExp) {
9340
    if (!el.getAttribute || !el.getAttribute('style')) {
9341
      return false;
9342
    }
9343
    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...
9344
    return  (el.getAttribute('style').match(regExp)) ? true : false;
9345
  }
9346
9347
  function addStyle(el, cssStyle, regExp) {
9348
    if (el.getAttribute('style')) {
9349
      removeStyle(el, regExp);
9350
      if (el.getAttribute('style') && !(/^\s*$/).test(el.getAttribute('style'))) {
9351
        el.setAttribute('style', cssStyle + ";" + el.getAttribute('style'));
9352
      } else {
9353
        el.setAttribute('style', cssStyle);
9354
      }
9355
    } else {
9356
      el.setAttribute('style', cssStyle);
9357
    }
9358
  }
9359
9360
  function addClass(el, cssClass, regExp) {
9361
    if (el.className) {
9362
      removeClass(el, regExp);
9363
      el.className += " " + cssClass;
9364
    } else {
9365
      el.className = cssClass;
9366
    }
9367
  }
9368
9369
  function removeClass(el, regExp) {
9370
    if (el.className) {
9371
      el.className = el.className.replace(regExp, "");
9372
    }
9373
  }
9374
9375
  function removeStyle(el, regExp) {
9376
    var s,
9377
        s2 = [];
9378
    if (el.getAttribute('style')) {
9379
      s = el.getAttribute('style').split(';');
9380
      for (var i = s.length; i--;) {
9381
        if (!s[i].match(regExp) && !(/^\s*$/).test(s[i])) {
9382
          s2.push(s[i]);
9383
        }
9384
      }
9385
      if (s2.length) {
9386
        el.setAttribute('style', s2.join(';'));
9387
      } else {
9388
        el.removeAttribute('style');
9389
      }
9390
    }
9391
  }
9392
9393
  function getMatchingStyleRegexp(el, style) {
9394
    var regexes = [],
9395
        sSplit = style.split(';'),
9396
        elStyle = el.getAttribute('style');
9397
9398
    if (elStyle) {
9399
      elStyle = elStyle.replace(/\s/gi, '').toLowerCase();
9400
      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"));
9401
9402
      for (var i = sSplit.length; i-- > 0;) {
9403
        if (!(/^\s*$/).test(sSplit[i])) {
9404
          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"));
9405
        }
9406
      }
9407
      for (var j = 0, jmax = regexes.length; j < jmax; j++) {
9408
        if (elStyle.match(regexes[j])) {
9409
          return regexes[j];
9410
        }
9411
      }
9412
    }
9413
9414
    return false;
9415
  }
9416
9417
  function isMatchingAllready(node, tags, style, className) {
9418
    if (style) {
9419
      return getMatchingStyleRegexp(node, style);
9420
    } else if (className) {
9421
      return wysihtml5.dom.hasClass(node, className);
9422
    } else {
9423
      return rangy.dom.arrayContains(tags, node.tagName.toLowerCase());
9424
    }
9425
  }
9426
9427
  function areMatchingAllready(nodes, tags, style, className) {
9428
    for (var i = nodes.length; i--;) {
9429
      if (!isMatchingAllready(nodes[i], tags, style, className)) {
9430
        return false;
9431
      }
9432
    }
9433
    return nodes.length ? true : false;
9434
  }
9435
9436
  function removeOrChangeStyle(el, style, regExp) {
9437
9438
    var exactRegex = getMatchingStyleRegexp(el, style);
9439
    if (exactRegex) {
9440
      // adding same style value on property again removes style
9441
      removeStyle(el, exactRegex);
9442
      return "remove";
9443
    } 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...
9444
      // adding new style value changes value
9445
      addStyle(el, style, regExp);
9446
      return "change";
9447
    }
9448
  }
9449
9450
  function hasSameClasses(el1, el2) {
9451
    return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " ");
9452
  }
9453
9454
  function replaceWithOwnChildren(el) {
9455
    var parent = el.parentNode;
9456
    while (el.firstChild) {
9457
      parent.insertBefore(el.firstChild, el);
9458
    }
9459
    parent.removeChild(el);
9460
  }
9461
9462
  function elementsHaveSameNonClassAttributes(el1, el2) {
9463
    if (el1.attributes.length != el2.attributes.length) {
9464
      return false;
9465
    }
9466
    for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
9467
      attr1 = el1.attributes[i];
9468
      name = attr1.name;
9469
      if (name != "class") {
9470
        attr2 = el2.attributes.getNamedItem(name);
9471
        if (attr1.specified != attr2.specified) {
9472
          return false;
9473
        }
9474
        if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
9475
          return false;
9476
        }
9477
      }
9478
    }
9479
    return true;
9480
  }
9481
9482
  function isSplitPoint(node, offset) {
9483
    if (rangy.dom.isCharacterDataNode(node)) {
9484
      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...
9485
        return !!node.previousSibling;
9486
      } else if (offset == node.length) {
9487
        return !!node.nextSibling;
9488
      } else {
9489
        return true;
9490
      }
9491
    }
9492
9493
    return offset > 0 && offset < node.childNodes.length;
9494
  }
9495
9496
  function splitNodeAt(node, descendantNode, descendantOffset, container) {
9497
    var newNode;
9498
    if (rangy.dom.isCharacterDataNode(descendantNode)) {
9499
      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...
9500
        descendantOffset = rangy.dom.getNodeIndex(descendantNode);
9501
        descendantNode = descendantNode.parentNode;
9502
      } else if (descendantOffset == descendantNode.length) {
9503
        descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
9504
        descendantNode = descendantNode.parentNode;
9505
      } else {
9506
        newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
9507
      }
9508
    }
9509
    if (!newNode) {
9510
      if (!container || descendantNode !== container) {
9511
9512
        newNode = descendantNode.cloneNode(false);
9513
        if (newNode.id) {
9514
          newNode.removeAttribute("id");
9515
        }
9516
        var child;
9517
        while ((child = descendantNode.childNodes[descendantOffset])) {
9518
          newNode.appendChild(child);
9519
        }
9520
        rangy.dom.insertAfter(newNode, descendantNode);
9521
9522
      }
9523
    }
9524
    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...
9525
  }
9526
9527
  function Merge(firstNode) {
9528
    this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
9529
    this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
9530
    this.textNodes = [this.firstTextNode];
9531
  }
9532
9533
  Merge.prototype = {
9534
    doMerge: function() {
9535
      var textBits = [], textNode, parent, text;
9536
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9537
        textNode = this.textNodes[i];
9538
        parent = textNode.parentNode;
9539
        textBits[i] = textNode.data;
9540
        if (i) {
9541
          parent.removeChild(textNode);
9542
          if (!parent.hasChildNodes()) {
9543
            parent.parentNode.removeChild(parent);
9544
          }
9545
        }
9546
      }
9547
      this.firstTextNode.data = text = textBits.join("");
9548
      return text;
9549
    },
9550
9551
    getLength: function() {
9552
      var i = this.textNodes.length, len = 0;
9553
      while (i--) {
9554
        len += this.textNodes[i].length;
9555
      }
9556
      return len;
9557
    },
9558
9559
    toString: function() {
9560
      var textBits = [];
9561
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9562
        textBits[i] = "'" + this.textNodes[i].data + "'";
9563
      }
9564
      return "[Merge(" + textBits.join(",") + ")]";
9565
    }
9566
  };
9567
9568
  function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize, cssStyle, similarStyleRegExp, container) {
9569
    this.tagNames = tagNames || [defaultTagName];
9570
    this.cssClass = cssClass || ((cssClass === false) ? false : "");
9571
    this.similarClassRegExp = similarClassRegExp;
9572
    this.cssStyle = cssStyle || "";
9573
    this.similarStyleRegExp = similarStyleRegExp;
9574
    this.normalize = normalize;
9575
    this.applyToAnyTagName = false;
9576
    this.container = container;
9577
  }
9578
9579
  HTMLApplier.prototype = {
9580
    getAncestorWithClass: function(node) {
9581
      var cssClassMatch;
9582
      while (node) {
9583
        cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : (this.cssStyle !== "") ? false : true;
9584
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" &&  rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
9585
          return node;
9586
        }
9587
        node = node.parentNode;
9588
      }
9589
      return false;
9590
    },
9591
9592
    // returns parents of node with given style attribute
9593
    getAncestorWithStyle: function(node) {
9594
      var cssStyleMatch;
9595
      while (node) {
9596
        cssStyleMatch = this.cssStyle ? hasStyleAttr(node, this.similarStyleRegExp) : false;
9597
9598
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssStyleMatch) {
9599
          return node;
9600
        }
9601
        node = node.parentNode;
9602
      }
9603
      return false;
9604
    },
9605
9606
    getMatchingAncestor: function(node) {
9607
      var ancestor = this.getAncestorWithClass(node),
9608
          matchType = false;
9609
9610
      if (!ancestor) {
9611
        ancestor = this.getAncestorWithStyle(node);
9612
        if (ancestor) {
9613
          matchType = "style";
9614
        }
9615
      } else {
9616
        if (this.cssStyle) {
9617
          matchType = "class";
9618
        }
9619
      }
9620
9621
      return {
9622
        "element": ancestor,
9623
        "type": matchType
9624
      };
9625
    },
9626
9627
    // Normalizes nodes after applying a CSS class to a Range.
9628
    postApply: function(textNodes, range) {
9629
      var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
9630
9631
      var merges = [], currentMerge;
9632
9633
      var rangeStartNode = firstNode, rangeEndNode = lastNode;
9634
      var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
9635
9636
      var textNode, precedingTextNode;
9637
9638
      for (var i = 0, len = textNodes.length; i < len; ++i) {
9639
        textNode = textNodes[i];
9640
        precedingTextNode = null;
9641
        if (textNode && textNode.parentNode) {
9642
          precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false);
9643
        }
9644
        if (precedingTextNode) {
9645
          if (!currentMerge) {
9646
            currentMerge = new Merge(precedingTextNode);
9647
            merges.push(currentMerge);
9648
          }
9649
          currentMerge.textNodes.push(textNode);
9650
          if (textNode === firstNode) {
9651
            rangeStartNode = currentMerge.firstTextNode;
9652
            rangeStartOffset = rangeStartNode.length;
9653
          }
9654
          if (textNode === lastNode) {
9655
            rangeEndNode = currentMerge.firstTextNode;
9656
            rangeEndOffset = currentMerge.getLength();
9657
          }
9658
        } else {
9659
          currentMerge = null;
9660
        }
9661
      }
9662
      // Test whether the first node after the range needs merging
9663
      if(lastNode && lastNode.parentNode) {
9664
        var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true);
9665
        if (nextTextNode) {
9666
          if (!currentMerge) {
9667
            currentMerge = new Merge(lastNode);
9668
            merges.push(currentMerge);
9669
          }
9670
          currentMerge.textNodes.push(nextTextNode);
9671
        }
9672
      }
9673
      // Do the merges
9674
      if (merges.length) {
9675
        for (i = 0, len = merges.length; i < len; ++i) {
9676
          merges[i].doMerge();
9677
        }
9678
        // Set the range boundaries
9679
        range.setStart(rangeStartNode, rangeStartOffset);
9680
        range.setEnd(rangeEndNode, rangeEndOffset);
9681
      }
9682
    },
9683
9684
    getAdjacentMergeableTextNode: function(node, forward) {
9685
        var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
9686
        var el = isTextNode ? node.parentNode : node;
9687
        var adjacentNode;
9688
        var propName = forward ? "nextSibling" : "previousSibling";
9689
        if (isTextNode) {
9690
          // Can merge if the node's previous/next sibling is a text node
9691
          adjacentNode = node[propName];
9692
          if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
9693
            return adjacentNode;
9694
          }
9695
        } else {
9696
          // Compare element with its sibling
9697
          adjacentNode = el[propName];
9698
          if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) {
9699
            return adjacentNode[forward ? "firstChild" : "lastChild"];
9700
          }
9701
        }
9702
        return null;
9703
    },
9704
9705
    areElementsMergeable: function(el1, el2) {
9706
      return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
9707
        && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
9708
        && hasSameClasses(el1, el2)
9709
        && elementsHaveSameNonClassAttributes(el1, el2);
9710
    },
9711
9712
    createContainer: function(doc) {
9713
      var el = doc.createElement(this.tagNames[0]);
9714
      if (this.cssClass) {
9715
        el.className = this.cssClass;
9716
      }
9717
      if (this.cssStyle) {
9718
        el.setAttribute('style', this.cssStyle);
9719
      }
9720
      return el;
9721
    },
9722
9723
    applyToTextNode: function(textNode) {
9724
      var parent = textNode.parentNode;
9725
      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...
9726
9727
        if (this.cssClass) {
9728
          addClass(parent, this.cssClass, this.similarClassRegExp);
9729
        }
9730
        if (this.cssStyle) {
9731
          addStyle(parent, this.cssStyle, this.similarStyleRegExp);
9732
        }
9733
      } else {
9734
        var el = this.createContainer(rangy.dom.getDocument(textNode));
9735
        textNode.parentNode.insertBefore(el, textNode);
9736
        el.appendChild(textNode);
9737
      }
9738
    },
9739
9740
    isRemovable: function(el) {
9741
      return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) &&
9742
              wysihtml5.lang.string(el.className).trim() === "" &&
9743
              (
9744
                !el.getAttribute('style') ||
9745
                wysihtml5.lang.string(el.getAttribute('style')).trim() === ""
9746
              );
9747
    },
9748
9749
    undoToTextNode: function(textNode, range, ancestorWithClass, ancestorWithStyle) {
9750
      var styleMode = (ancestorWithClass) ? false : true,
9751
          ancestor = ancestorWithClass || ancestorWithStyle,
9752
          styleChanged = false;
9753
      if (!range.containsNode(ancestor)) {
9754
        // Split out the portion of the ancestor from which we can remove the CSS class
9755
        var ancestorRange = range.cloneRange();
9756
            ancestorRange.selectNode(ancestor);
9757
9758
        if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) {
9759
            splitNodeAt(ancestor, range.endContainer, range.endOffset, this.container);
9760
            range.setEndAfter(ancestor);
9761
        }
9762
        if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
9763
            ancestor = splitNodeAt(ancestor, range.startContainer, range.startOffset, this.container);
9764
        }
9765
      }
9766
9767
      if (!styleMode && this.similarClassRegExp) {
9768
        removeClass(ancestor, this.similarClassRegExp);
9769
      }
9770
9771
      if (styleMode && this.similarStyleRegExp) {
9772
        styleChanged = (removeOrChangeStyle(ancestor, this.cssStyle, this.similarStyleRegExp) === "change");
9773
      }
9774
      if (this.isRemovable(ancestor) && !styleChanged) {
9775
        replaceWithOwnChildren(ancestor);
9776
      }
9777
    },
9778
9779
    applyToRange: function(range) {
9780
        var textNodes;
9781
        for (var ri = range.length; ri--;) {
9782
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9783
9784
            if (!textNodes.length) {
9785
              try {
9786
                var node = this.createContainer(range[ri].endContainer.ownerDocument);
9787
                range[ri].surroundContents(node);
9788
                this.selectNode(range[ri], node);
9789
                return;
9790
              } 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...
9791
            }
9792
9793
            range[ri].splitBoundaries();
9794
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9795
            if (textNodes.length) {
9796
              var textNode;
9797
9798
              for (var i = 0, len = textNodes.length; i < len; ++i) {
9799
                textNode = textNodes[i];
9800
                if (!this.getMatchingAncestor(textNode).element) {
9801
                  this.applyToTextNode(textNode);
9802
                }
9803
              }
9804
9805
              range[ri].setStart(textNodes[0], 0);
9806
              textNode = textNodes[textNodes.length - 1];
9807
              range[ri].setEnd(textNode, textNode.length);
9808
9809
              if (this.normalize) {
9810
                this.postApply(textNodes, range[ri]);
9811
              }
9812
            }
9813
9814
        }
9815
    },
9816
9817
    undoToRange: function(range) {
9818
      var textNodes, textNode, ancestorWithClass, ancestorWithStyle, ancestor;
0 ignored issues
show
Unused Code introduced by
The variable ancestorWithClass seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable ancestorWithStyle seems to be never used. Consider removing it.
Loading history...
9819
      for (var ri = range.length; ri--;) {
9820
9821
          textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9822
          if (textNodes.length) {
9823
            range[ri].splitBoundaries();
9824
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9825
          } else {
9826
            var doc = range[ri].endContainer.ownerDocument,
9827
                node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
9828
            range[ri].insertNode(node);
9829
            range[ri].selectNode(node);
9830
            textNodes = [node];
9831
          }
9832
9833
          for (var i = 0, len = textNodes.length; i < len; ++i) {
9834
            if (range[ri].isValid()) {
9835
              textNode = textNodes[i];
9836
9837
              ancestor = this.getMatchingAncestor(textNode);
9838
              if (ancestor.type === "style") {
9839
                this.undoToTextNode(textNode, range[ri], false, ancestor.element);
9840
              } else if (ancestor.element) {
9841
                this.undoToTextNode(textNode, range[ri], ancestor.element);
9842
              }
9843
            }
9844
          }
9845
9846
          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...
9847
            this.selectNode(range[ri], textNodes[0]);
9848
          } else {
9849
            range[ri].setStart(textNodes[0], 0);
9850
            textNode = textNodes[textNodes.length - 1];
9851
            range[ri].setEnd(textNode, textNode.length);
9852
9853
            if (this.normalize) {
9854
              this.postApply(textNodes, range[ri]);
9855
            }
9856
          }
9857
9858
      }
9859
    },
9860
9861
    selectNode: function(range, node) {
9862
      var isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
9863
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : true,
9864
          content         = isElement ? node.innerHTML : node.data,
9865
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE);
9866
9867
      if (isEmpty && isElement && canHaveHTML) {
9868
        // Make sure that caret is visible in node by inserting a zero width no breaking space
9869
        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...
9870
      }
9871
      range.selectNodeContents(node);
9872
      if (isEmpty && isElement) {
9873
        range.collapse(false);
9874
      } else if (isEmpty) {
9875
        range.setStartAfter(node);
9876
        range.setEndAfter(node);
9877
      }
9878
    },
9879
9880
    getTextSelectedByRange: function(textNode, range) {
9881
      var textRange = range.cloneRange();
9882
      textRange.selectNodeContents(textNode);
9883
9884
      var intersectionRange = textRange.intersection(range);
9885
      var text = intersectionRange ? intersectionRange.toString() : "";
9886
      textRange.detach();
9887
9888
      return text;
9889
    },
9890
9891
    isAppliedToRange: function(range) {
9892
      var ancestors = [],
9893
          appliedType = "full",
9894
          ancestor, styleAncestor, textNodes;
0 ignored issues
show
Unused Code introduced by
The variable styleAncestor seems to be never used. Consider removing it.
Loading history...
9895
9896
      for (var ri = range.length; ri--;) {
9897
9898
        textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
9899
        if (!textNodes.length) {
9900
          ancestor = this.getMatchingAncestor(range[ri].startContainer).element;
9901
9902
          return (ancestor) ? {
9903
            "elements": [ancestor],
9904
            "coverage": appliedType
9905
          } : false;
9906
        }
9907
9908
        for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
9909
          selectedText = this.getTextSelectedByRange(textNodes[i], range[ri]);
9910
          ancestor = this.getMatchingAncestor(textNodes[i]).element;
9911
          if (ancestor && selectedText != "") {
9912
            ancestors.push(ancestor);
9913
9914
            if (wysihtml5.dom.getTextNodes(ancestor, true).length === 1) {
9915
              appliedType = "full";
9916
            } else if (appliedType === "full") {
9917
              appliedType = "inline";
9918
            }
9919
          } else if (!ancestor) {
9920
            appliedType = "partial";
9921
          }
9922
        }
9923
9924
      }
9925
9926
      return (ancestors.length) ? {
9927
        "elements": ancestors,
9928
        "coverage": appliedType
9929
      } : false;
9930
    },
9931
9932
    toggleRange: function(range) {
9933
      var isApplied = this.isAppliedToRange(range),
9934
          parentsExactMatch;
9935
9936
      if (isApplied) {
9937
        if (isApplied.coverage === "full") {
9938
          this.undoToRange(range);
9939
        } else if (isApplied.coverage === "inline") {
9940
          parentsExactMatch = areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass);
9941
          this.undoToRange(range);
9942
          if (!parentsExactMatch) {
9943
            this.applyToRange(range);
9944
          }
9945
        } else {
9946
          // partial
9947
          if (!areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass)) {
9948
            this.undoToRange(range);
9949
          }
9950
          this.applyToRange(range);
9951
        }
9952
      } else {
9953
        this.applyToRange(range);
9954
      }
9955
    }
9956
  };
9957
9958
  wysihtml5.selection.HTMLApplier = HTMLApplier;
9959
9960
})(wysihtml5, rangy);
9961
;/**
9962
 * Rich Text Query/Formatting Commands
9963
 *
9964
 * @example
9965
 *    var commands = new wysihtml5.Commands(editor);
9966
 */
9967
wysihtml5.Commands = Base.extend(
9968
  /** @scope wysihtml5.Commands.prototype */ {
9969
  constructor: function(editor) {
9970
    this.editor   = editor;
9971
    this.composer = editor.composer;
9972
    this.doc      = this.composer.doc;
9973
  },
9974
9975
  /**
9976
   * Check whether the browser supports the given command
9977
   *
9978
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
9979
   * @example
9980
   *    commands.supports("createLink");
9981
   */
9982
  support: function(command) {
9983
    return wysihtml5.browser.supportsCommand(this.doc, command);
9984
  },
9985
9986
  /**
9987
   * Check whether the browser supports the given command
9988
   *
9989
   * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
9990
   * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
9991
   * @example
9992
   *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
9993
   */
9994
  exec: function(command, value) {
9995
    var obj     = wysihtml5.commands[command],
9996
        args    = wysihtml5.lang.array(arguments).get(),
9997
        method  = obj && obj.exec,
9998
        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...
9999
10000
    this.editor.fire("beforecommand:composer");
10001
10002
    if (method) {
10003
      args.unshift(this.composer);
10004
      result = method.apply(obj, args);
10005
    } else {
10006
      try {
10007
        // try/catch for buggy firefox
10008
        result = this.doc.execCommand(command, false, value);
10009
      } 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...
10010
    }
10011
10012
    this.editor.fire("aftercommand:composer");
10013
    return result;
10014
  },
10015
10016
  /**
10017
   * Check whether the current command is active
10018
   * If the caret is within a bold text, then calling this with command "bold" should return true
10019
   *
10020
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
10021
   * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
10022
   * @return {Boolean} Whether the command is active
10023
   * @example
10024
   *    var isCurrentSelectionBold = commands.state("bold");
10025
   */
10026
  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...
10027
    var obj     = wysihtml5.commands[command],
10028
        args    = wysihtml5.lang.array(arguments).get(),
10029
        method  = obj && obj.state;
10030
    if (method) {
10031
      args.unshift(this.composer);
10032
      return method.apply(obj, args);
10033
    } 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...
10034
      try {
10035
        // try/catch for buggy firefox
10036
        return this.doc.queryCommandState(command);
10037
      } catch(e) {
10038
        return false;
10039
      }
10040
    }
10041
  },
10042
10043
  /* Get command state parsed value if command has stateValue parsing function */
10044
  stateValue: function(command) {
10045
    var obj     = wysihtml5.commands[command],
10046
        args    = wysihtml5.lang.array(arguments).get(),
10047
        method  = obj && obj.stateValue;
10048
    if (method) {
10049
      args.unshift(this.composer);
10050
      return method.apply(obj, args);
10051
    } 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...
10052
      return false;
10053
    }
10054
  }
10055
});
10056
;wysihtml5.commands.bold = {
10057
  exec: function(composer, command) {
10058
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "b");
10059
  },
10060
10061
  state: function(composer, command) {
10062
    // element.ownerDocument.queryCommandState("bold") results:
10063
    // firefox: only <b>
10064
    // chrome:  <b>, <strong>, <h1>, <h2>, ...
10065
    // ie:      <b>, <strong>
10066
    // opera:   <b>, <strong>
10067
    return wysihtml5.commands.formatInline.state(composer, command, "b");
10068
  }
10069
};
10070
10071
;(function(wysihtml5) {
10072
  var undef,
10073
      NODE_NAME = "A",
10074
      dom       = wysihtml5.dom;
10075
10076
  function _format(composer, attributes) {
10077
    var doc             = composer.doc,
10078
        tempClass       = "_wysihtml5-temp-" + (+new Date()),
10079
        tempClassRegExp = /non-matching-class/g,
10080
        i               = 0,
10081
        length,
10082
        anchors,
10083
        anchor,
10084
        hasElementChild,
10085
        isEmpty,
10086
        elementToSetCaretAfter,
10087
        textContent,
10088
        whiteSpace,
10089
        j;
10090
    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...
10091
    anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
10092
    length  = anchors.length;
10093
    for (; i<length; i++) {
10094
      anchor = anchors[i];
10095
      anchor.removeAttribute("class");
10096
      for (j in attributes) {
10097
        // Do not set attribute "text" as it is meant for setting string value if created link has no textual data
10098
        if (j !== "text") {
10099
          anchor.setAttribute(j, attributes[j]);
10100
        }
10101
      }
10102
    }
10103
10104
    elementToSetCaretAfter = anchor;
0 ignored issues
show
Bug introduced by
The variable anchor seems to not be initialized for all possible execution paths.
Loading history...
10105
    if (length === 1) {
10106
      textContent = dom.getTextContent(anchor);
10107
      hasElementChild = !!anchor.querySelector("*");
10108
      isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
10109
      if (!hasElementChild && isEmpty) {
10110
        dom.setTextContent(anchor, attributes.text || anchor.href);
10111
        whiteSpace = doc.createTextNode(" ");
10112
        composer.selection.setAfter(anchor);
10113
        dom.insert(whiteSpace).after(anchor);
10114
        elementToSetCaretAfter = whiteSpace;
10115
      }
10116
    }
10117
    composer.selection.setAfter(elementToSetCaretAfter);
10118
  }
10119
10120
  // Changes attributes of links
10121
  function _changeLinks(composer, anchors, attributes) {
10122
    var oldAttrs;
10123
    for (var a = anchors.length; a--;) {
10124
10125
      // Remove all old attributes
10126
      oldAttrs = anchors[a].attributes;
10127
      for (var oa = oldAttrs.length; oa--;) {
10128
        anchors[a].removeAttribute(oldAttrs.item(oa).name);
10129
      }
10130
10131
      // Set new attributes
10132
      for (var j in attributes) {
10133
        if (attributes.hasOwnProperty(j)) {
10134
          anchors[a].setAttribute(j, attributes[j]);
10135
        }
10136
      }
10137
10138
    }
10139
  }
10140
10141
  wysihtml5.commands.createLink = {
10142
    /**
10143
     * TODO: Use HTMLApplier or formatInline here
10144
     *
10145
     * Turns selection into a link
10146
     * If selection is already a link, it just changes the attributes
10147
     *
10148
     * @example
10149
     *    // either ...
10150
     *    wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
10151
     *    // ... or ...
10152
     *    wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
10153
     */
10154
    exec: function(composer, command, value) {
10155
      var anchors = this.state(composer, command);
10156
      if (anchors) {
10157
        // Selection contains links then change attributes of these links
10158
        composer.selection.executeAndRestore(function() {
10159
          _changeLinks(composer, anchors, value);
10160
        });
10161
      } else {
10162
        // Create links
10163
        value = typeof(value) === "object" ? value : { href: value };
10164
        _format(composer, value);
10165
      }
10166
    },
10167
10168
    state: function(composer, command) {
10169
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10170
    }
10171
  };
10172
})(wysihtml5);
10173
;(function(wysihtml5) {
10174
  var dom = wysihtml5.dom;
10175
10176
  function _removeFormat(composer, anchors) {
10177
    var length  = anchors.length,
10178
        i       = 0,
10179
        anchor,
10180
        codeElement,
10181
        textContent;
10182
    for (; i<length; i++) {
10183
      anchor      = anchors[i];
10184
      codeElement = dom.getParentElement(anchor, { nodeName: "code" });
10185
      textContent = dom.getTextContent(anchor);
10186
10187
      // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking
10188
      // else replace <a> with its childNodes
10189
      if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) {
10190
        // <code> element is used to prevent later auto-linking of the content
10191
        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...
10192
      } else {
10193
        dom.replaceWithChildNodes(anchor);
10194
      }
10195
    }
10196
  }
10197
10198
  wysihtml5.commands.removeLink = {
10199
    /*
10200
     * If selection is a link, it removes the link and wraps it with a <code> element
10201
     * The <code> element is needed to avoid auto linking
10202
     *
10203
     * @example
10204
     *    wysihtml5.commands.createLink.exec(composer, "removeLink");
10205
     */
10206
10207
    exec: function(composer, command) {
10208
      var anchors = this.state(composer, command);
10209
      if (anchors) {
10210
        composer.selection.executeAndRestore(function() {
10211
          _removeFormat(composer, anchors);
10212
        });
10213
      }
10214
    },
10215
10216
    state: function(composer, command) {
10217
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10218
    }
10219
  };
10220
})(wysihtml5);
10221
;/**
10222
 * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
10223
 * which we don't want
10224
 * Instead we set a css class
10225
 */
10226
(function(wysihtml5) {
10227
  var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
10228
10229
  wysihtml5.commands.fontSize = {
10230
    exec: function(composer, command, size) {
10231
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10232
    },
10233
10234
    state: function(composer, command, size) {
10235
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10236
    }
10237
  };
10238
})(wysihtml5);
10239
;/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
10240
(function(wysihtml5) {
10241
  var REG_EXP = /(\s|^)font-size\s*:\s*[^;\s]+;?/gi;
10242
10243
  wysihtml5.commands.fontSizeStyle = {
10244
    exec: function(composer, command, size) {
10245
      size = (typeof(size) == "object") ? size.size : size;
10246
      if (!(/^\s*$/).test(size)) {
10247
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, "font-size:" + size, REG_EXP);
10248
      }
10249
    },
10250
10251
    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...
10252
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "font-size", REG_EXP);
10253
    },
10254
10255
    stateValue: function(composer, command) {
10256
      var st = this.state(composer, command),
10257
          styleStr, fontsizeMatches,
0 ignored issues
show
Unused Code introduced by
The variable fontsizeMatches seems to be never used. Consider removing it.
Loading history...
10258
          val = false;
0 ignored issues
show
Unused Code introduced by
The variable val seems to be never used. Consider removing it.
Loading history...
10259
10260
      if (st && wysihtml5.lang.object(st).isArray()) {
10261
          st = st[0];
10262
      }
10263
      if (st) {
10264
        styleStr = st.getAttribute('style');
10265
        if (styleStr) {
10266
          return wysihtml5.quirks.styleParser.parseFontSize(styleStr);
10267
        }
10268
      }
10269
      return false;
10270
    }
10271
  };
10272
})(wysihtml5);
10273
;/**
10274
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10275
 * which we don't want
10276
 * Instead we set a css class
10277
 */
10278
(function(wysihtml5) {
10279
  var REG_EXP = /wysiwyg-color-[0-9a-z]+/g;
10280
10281
  wysihtml5.commands.foreColor = {
10282
    exec: function(composer, command, color) {
10283
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10284
    },
10285
10286
    state: function(composer, command, color) {
10287
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10288
    }
10289
  };
10290
})(wysihtml5);
10291
;/**
10292
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10293
 * which we don't want
10294
 * Instead we set a css class
10295
 */
10296
(function(wysihtml5) {
10297
  var REG_EXP = /(\s|^)color\s*:\s*[^;\s]+;?/gi;
10298
10299
  wysihtml5.commands.foreColorStyle = {
10300
    exec: function(composer, command, color) {
10301
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "color:" + color.color : "color:" + color, "color"),
10302
          colString;
10303
10304
      if (colorVals) {
10305
        colString = "color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10306
        if (colorVals[3] !== 1) {
10307
          colString += "color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10308
        }
10309
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10310
      }
10311
    },
10312
10313
    state: function(composer, command) {
10314
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "color", REG_EXP);
10315
    },
10316
10317
    stateValue: function(composer, command, props) {
10318
      var st = this.state(composer, command),
10319
          colorStr;
10320
10321
      if (st && wysihtml5.lang.object(st).isArray()) {
10322
        st = st[0];
10323
      }
10324
10325
      if (st) {
10326
        colorStr = st.getAttribute('style');
10327
        if (colorStr) {
10328
          if (colorStr) {
10329
            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...
10330
            return wysihtml5.quirks.styleParser.unparseColor(val, props);
10331
          }
10332
        }
10333
      }
10334
      return false;
10335
    }
10336
10337
  };
10338
})(wysihtml5);
10339
;/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
10340
(function(wysihtml5) {
10341
  var REG_EXP = /(\s|^)background-color\s*:\s*[^;\s]+;?/gi;
10342
10343
  wysihtml5.commands.bgColorStyle = {
10344
    exec: function(composer, command, color) {
10345
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "background-color:" + color.color : "background-color:" + color, "background-color"),
10346
          colString;
10347
10348
      if (colorVals) {
10349
        colString = "background-color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10350
        if (colorVals[3] !== 1) {
10351
          colString += "background-color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10352
        }
10353
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10354
      }
10355
    },
10356
10357
    state: function(composer, command) {
10358
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "background-color", REG_EXP);
10359
    },
10360
10361
    stateValue: function(composer, command, props) {
10362
      var st = this.state(composer, command),
10363
          colorStr,
10364
          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...
10365
10366
      if (st && wysihtml5.lang.object(st).isArray()) {
10367
        st = st[0];
10368
      }
10369
10370
      if (st) {
10371
        colorStr = st.getAttribute('style');
10372
        if (colorStr) {
10373
          val = wysihtml5.quirks.styleParser.parseColor(colorStr, "background-color");
10374
          return wysihtml5.quirks.styleParser.unparseColor(val, props);
10375
        }
10376
      }
10377
      return false;
10378
    }
10379
10380
  };
10381
})(wysihtml5);
10382
;(function(wysihtml5) {
10383
  var dom                     = wysihtml5.dom,
10384
      // Following elements are grouped
10385
      // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
10386
      // instead of creating a H4 within a H1 which would result in semantically invalid html
10387
      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "DIV"];
10388
10389
  /**
10390
   * Remove similiar classes (based on classRegExp)
10391
   * and add the desired class name
10392
   */
10393
  function _addClass(element, className, classRegExp) {
10394
    if (element.className) {
10395
      _removeClass(element, classRegExp);
10396
      element.className = wysihtml5.lang.string(element.className + " " + className).trim();
10397
    } else {
10398
      element.className = className;
10399
    }
10400
  }
10401
10402
  function _addStyle(element, cssStyle, styleRegExp) {
10403
    _removeStyle(element, styleRegExp);
10404
    if (element.getAttribute('style')) {
10405
      element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
10406
    } else {
10407
      element.setAttribute('style', cssStyle);
10408
    }
10409
  }
10410
10411
  function _removeClass(element, classRegExp) {
10412
    var ret = classRegExp.test(element.className);
10413
    element.className = element.className.replace(classRegExp, "");
10414
    if (wysihtml5.lang.string(element.className).trim() == '') {
10415
        element.removeAttribute('class');
10416
    }
10417
    return ret;
10418
  }
10419
10420
  function _removeStyle(element, styleRegExp) {
10421
    var ret = styleRegExp.test(element.getAttribute('style'));
10422
    element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
10423
    if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
10424
      element.removeAttribute('style');
10425
    }
10426
    return ret;
10427
  }
10428
10429
  function _removeLastChildIfLineBreak(node) {
10430
    var lastChild = node.lastChild;
10431
    if (lastChild && _isLineBreak(lastChild)) {
10432
      lastChild.parentNode.removeChild(lastChild);
10433
    }
10434
  }
10435
10436
  function _isLineBreak(node) {
10437
    return node.nodeName === "BR";
10438
  }
10439
10440
  /**
10441
   * Execute native query command
10442
   * and if necessary modify the inserted node's className
10443
   */
10444
  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...
10445
    var ranges = composer.selection.getOwnRanges();
10446
    for (var i = ranges.length; i--;){
10447
      composer.selection.getSelection().removeAllRanges();
10448
      composer.selection.setSelection(ranges[i]);
10449
      if (className) {
10450
        var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
10451
          var target = event.target,
10452
              displayStyle;
10453
          if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
10454
            return;
10455
          }
10456
          displayStyle = dom.getStyle("display").from(target);
10457
          if (displayStyle.substr(0, 6) !== "inline") {
10458
            // Make sure that only block elements receive the given class
10459
            target.className += " " + className;
10460
          }
10461
        });
10462
      }
10463
      doc.execCommand(command, false, nodeName);
10464
10465
      if (eventListener) {
10466
        eventListener.stop();
10467
      }
10468
    }
10469
  }
10470
10471
  function _selectionWrap(composer, options) {
10472
    if (composer.selection.isCollapsed()) {
10473
        composer.selection.selectLine();
10474
    }
10475
10476
    var surroundedNodes = composer.selection.surround(options);
10477
    for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
10478
      wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
10479
      _removeLastChildIfLineBreak(surroundedNodes[i]);
10480
    }
10481
10482
    // rethink restoring selection
10483
    // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
10484
  }
10485
10486
  function _hasClasses(element) {
10487
    return !!wysihtml5.lang.string(element.className).trim();
10488
  }
10489
10490
  function _hasStyles(element) {
10491
    return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
10492
  }
10493
10494
  wysihtml5.commands.formatBlock = {
10495
    exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10496
      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...
10497
          blockElements    = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
10498
          useLineBreaks   = composer.config.useLineBreaks,
10499
          defaultNodeName = useLineBreaks ? "DIV" : "P",
10500
          selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
10501
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10502
10503
      if (blockElements.length) {
10504
        composer.selection.executeAndRestoreRangy(function() {
10505
          for (var b = blockElements.length; b--;) {
10506
            if (classRegExp) {
10507
              classRemoveAction = _removeClass(blockElements[b], classRegExp);
10508
            }
10509
            if (styleRegExp) {
10510
              styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
10511
            }
10512
10513
            if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
10514
              // dont rename or remove element when just setting block formating class or style
10515
              return;
10516
            }
10517
10518
            var hasClasses = _hasClasses(blockElements[b]),
10519
                hasStyles = _hasStyles(blockElements[b]);
10520
10521
            if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
10522
              // Insert a line break afterwards and beforewards when there are siblings
10523
              // that are not of type line break or block element
10524
              wysihtml5.dom.lineBreaks(blockElements[b]).add();
10525
              dom.replaceWithChildNodes(blockElements[b]);
10526
            } else {
10527
              // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
10528
              dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
10529
            }
10530
          }
10531
        });
10532
10533
        return;
10534
      }
10535
10536
      // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
10537
      if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
10538
        selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
10539
        composer.selection.executeAndRestoreRangy(function() {
10540
          for (var n = selectedNodes.length; n--;) {
10541
            blockElement = dom.getParentElement(selectedNodes[n], {
10542
              nodeName: BLOCK_ELEMENTS_GROUP
10543
            });
10544
            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 10541. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
10545
              blockElement = null;
10546
            }
10547
            if (blockElement) {
10548
                // Rename current block element to new block element and add class
10549
                if (nodeName) {
10550
                  blockElement = dom.renameElement(blockElement, nodeName);
10551
                }
10552
                if (className) {
10553
                  _addClass(blockElement, className, classRegExp);
10554
                }
10555
                if (cssStyle) {
10556
                  _addStyle(blockElement, cssStyle, styleRegExp);
10557
                }
10558
              blockRenameFound = true;
10559
            }
10560
          }
10561
10562
        });
10563
10564
        if (blockRenameFound) {
10565
          return;
10566
        }
10567
      }
10568
10569
      _selectionWrap(composer, {
10570
        "nodeName": (nodeName || defaultNodeName),
10571
        "className": className || null,
10572
        "cssStyle": cssStyle || null
10573
      });
10574
    },
10575
10576
    state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10577
      var nodes = composer.selection.getSelectedOwnNodes(),
10578
          parents = [],
10579
          parent;
10580
10581
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10582
10583
      //var selectedNode = composer.selection.getSelectedNode();
10584
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
10585
        parent = dom.getParentElement(nodes[i], {
10586
          nodeName:     nodeName,
10587
          className:    className,
10588
          classRegExp:  classRegExp,
10589
          cssStyle:     cssStyle,
10590
          styleRegExp:  styleRegExp
10591
        });
10592
        if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
10593
          parents.push(parent);
10594
        }
10595
      }
10596
      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...
10597
        return false;
10598
      }
10599
      return parents;
10600
    }
10601
10602
10603
  };
10604
})(wysihtml5);
10605
;/* Formats block for as a <pre><code class="classname"></code></pre> block
10606
 * Useful in conjuction for sytax highlight utility: highlight.js
10607
 *
10608
 * Usage:
10609
 *
10610
 * editorInstance.composer.commands.exec("formatCode", "language-html");
10611
*/
10612
10613
wysihtml5.commands.formatCode = {
10614
10615
  exec: function(composer, command, classname) {
10616
    var pre = this.state(composer),
10617
        code, range, selectedNodes;
10618
    if (pre) {
10619
      // caret is already within a <pre><code>...</code></pre>
10620
      composer.selection.executeAndRestore(function() {
10621
        code = pre.querySelector("code");
10622
        wysihtml5.dom.replaceWithChildNodes(pre);
10623
        if (code) {
10624
          wysihtml5.dom.replaceWithChildNodes(code);
10625
        }
10626
      });
10627
    } else {
10628
      // Wrap in <pre><code>...</code></pre>
10629
      range = composer.selection.getRange();
10630
      selectedNodes = range.extractContents();
10631
      pre = composer.doc.createElement("pre");
10632
      code = composer.doc.createElement("code");
10633
10634
      if (classname) {
10635
        code.className = classname;
10636
      }
10637
10638
      pre.appendChild(code);
10639
      code.appendChild(selectedNodes);
10640
      range.insertNode(pre);
10641
      composer.selection.selectNode(pre);
10642
    }
10643
  },
10644
10645
  state: function(composer) {
10646
    var selectedNode = composer.selection.getSelectedNode();
10647
    if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
10648
        selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
10649
      return selectedNode;
10650
    } 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...
10651
      return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
10652
    }
10653
  }
10654
};;/**
10655
 * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
10656
 *
10657
 *   #1 caret in unformatted text:
10658
 *      abcdefg|
10659
 *   output:
10660
 *      abcdefg<b>|</b>
10661
 *
10662
 *   #2 unformatted text selected:
10663
 *      abc|deg|h
10664
 *   output:
10665
 *      abc<b>|deg|</b>h
10666
 *
10667
 *   #3 unformatted text selected across boundaries:
10668
 *      ab|c <span>defg|h</span>
10669
 *   output:
10670
 *      ab<b>|c </b><span><b>defg</b>|h</span>
10671
 *
10672
 *   #4 formatted text entirely selected
10673
 *      <b>|abc|</b>
10674
 *   output:
10675
 *      |abc|
10676
 *
10677
 *   #5 formatted text partially selected
10678
 *      <b>ab|c|</b>
10679
 *   output:
10680
 *      <b>ab</b>|c|
10681
 *
10682
 *   #6 formatted text selected across boundaries
10683
 *      <span>ab|c</span> <b>de|fgh</b>
10684
 *   output:
10685
 *      <span>ab|c</span> de|<b>fgh</b>
10686
 */
10687
(function(wysihtml5) {
10688
  var // Treat <b> as <strong> and vice versa
10689
      ALIAS_MAPPING = {
10690
        "strong": "b",
10691
        "em":     "i",
10692
        "b":      "strong",
10693
        "i":      "em"
10694
      },
10695
      htmlApplier = {};
10696
10697
  function _getTagNames(tagName) {
10698
    var alias = ALIAS_MAPPING[tagName];
10699
    return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
10700
  }
10701
10702
  function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
10703
    var identifier = tagName;
10704
    
10705
    if (className) {
10706
      identifier += ":" + className;
10707
    }
10708
    if (cssStyle) {
10709
      identifier += ":" + cssStyle;
10710
    }
10711
10712
    if (!htmlApplier[identifier]) {
10713
      htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
10714
    }
10715
10716
    return htmlApplier[identifier];
10717
  }
10718
10719
  wysihtml5.commands.formatInline = {
10720
    exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
10721
      var range = composer.selection.createRange(),
10722
          ownRanges = composer.selection.getOwnRanges();
10723
10724
      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...
10725
        return false;
10726
      }
10727
      composer.selection.getSelection().removeAllRanges();
10728
10729
      _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
10730
10731
      if (!dontRestoreSelect) {
10732
        range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
10733
        range.setEnd(
10734
          ownRanges[ownRanges.length - 1].endContainer,
10735
          ownRanges[ownRanges.length - 1].endOffset
10736
        );
10737
        composer.selection.setSelection(range);
10738
        composer.selection.executeAndRestore(function() {
10739
          if (!noCleanup) {
10740
            composer.cleanUp();
10741
          }
10742
        }, 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...
10743
      } 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...
10744
        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...
10745
      }
10746
    },
10747
10748
    // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
10749
    // It is achieved by selecting the entire state element before executing.
10750
    // This works on built in contenteditable inline format commands
10751
    execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
10752
      var that = this;
10753
10754
      if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
10755
        composer.selection.isCollapsed() &&
10756
        !composer.selection.caretIsLastInSelection() &&
10757
        !composer.selection.caretIsFirstInSelection()
10758
      ) {
10759
        var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
10760
        composer.selection.executeAndRestoreRangy(function() {
10761
          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...
10762
          composer.selection.selectNode(state_element, true);
10763
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10764
        });
10765
      } else {
10766
        if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
10767
          composer.selection.executeAndRestoreRangy(function() {
10768
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10769
          });
10770
        } else {
10771
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
10772
        }
10773
      }
10774
    },
10775
10776
    state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
10777
      var doc           = composer.doc,
10778
          aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
10779
          ownRanges, isApplied;
10780
10781
      // Check whether the document contains a node with the desired tagName
10782
      if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
10783
          !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
10784
        return false;
10785
      }
10786
10787
       // Check whether the document contains a node with the desired className
10788
      if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
10789
         return false;
10790
      }
10791
10792
      ownRanges = composer.selection.getOwnRanges();
10793
10794
      if (!ownRanges || ownRanges.length === 0) {
10795
        return false;
10796
      }
10797
10798
      isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
10799
10800
      return (isApplied && isApplied.elements) ? isApplied.elements : false;
10801
    }
10802
  };
10803
})(wysihtml5);
10804
;(function(wysihtml5) {
10805
10806
  wysihtml5.commands.insertBlockQuote = {
10807
    exec: function(composer, command) {
10808
      var state = this.state(composer, command),
10809
          endToEndParent = composer.selection.isEndToEndInNode(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P']),
10810
          prevNode, nextNode;
0 ignored issues
show
Unused Code introduced by
The variable prevNode seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable nextNode seems to be never used. Consider removing it.
Loading history...
10811
10812
      composer.selection.executeAndRestore(function() {
10813
        if (state) {
10814
          if (composer.config.useLineBreaks) {
10815
             wysihtml5.dom.lineBreaks(state).add();
10816
          }
10817
          wysihtml5.dom.unwrap(state);
10818
        } else {
10819
          if (composer.selection.isCollapsed()) {
10820
            composer.selection.selectLine();
10821
          }
10822
          
10823
          if (endToEndParent) {
10824
            var qouteEl = endToEndParent.ownerDocument.createElement('blockquote');
10825
            wysihtml5.dom.insert(qouteEl).after(endToEndParent);
10826
            qouteEl.appendChild(endToEndParent);
10827
          } else {
10828
            composer.selection.surround({nodeName: "blockquote"});
10829
          }
10830
        }
10831
      });
10832
    },
10833
    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...
10834
      var selectedNode  = composer.selection.getSelectedNode(),
10835
          node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "BLOCKQUOTE" }, false, composer.element);
10836
10837
      return (node) ? node : false;
10838
    }
10839
  };
10840
10841
})(wysihtml5);;wysihtml5.commands.insertHTML = {
10842
  exec: function(composer, command, html) {
10843
    if (composer.commands.support(command)) {
10844
      composer.doc.execCommand(command, false, html);
10845
    } else {
10846
      composer.selection.insertHTML(html);
10847
    }
10848
  },
10849
10850
  state: function() {
10851
    return false;
10852
  }
10853
};
10854
;(function(wysihtml5) {
10855
  var NODE_NAME = "IMG";
10856
10857
  wysihtml5.commands.insertImage = {
10858
    /**
10859
     * Inserts an <img>
10860
     * If selection is already an image link, it removes it
10861
     *
10862
     * @example
10863
     *    // either ...
10864
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
10865
     *    // ... or ...
10866
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
10867
     */
10868
    exec: function(composer, command, value) {
10869
      value = typeof(value) === "object" ? value : { src: value };
10870
10871
      var doc     = composer.doc,
10872
          image   = this.state(composer),
10873
          textNode,
10874
          parent;
10875
10876
      if (image) {
10877
        // Image already selected, set the caret before it and delete it
10878
        composer.selection.setBefore(image);
10879
        parent = image.parentNode;
10880
        parent.removeChild(image);
10881
10882
        // and it's parent <a> too if it hasn't got any other relevant child nodes
10883
        wysihtml5.dom.removeEmptyTextNodes(parent);
10884
        if (parent.nodeName === "A" && !parent.firstChild) {
10885
          composer.selection.setAfter(parent);
10886
          parent.parentNode.removeChild(parent);
10887
        }
10888
10889
        // firefox and ie sometimes don't remove the image handles, even though the image got removed
10890
        wysihtml5.quirks.redraw(composer.element);
10891
        return;
10892
      }
10893
10894
      image = doc.createElement(NODE_NAME);
10895
10896
      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...
10897
        image.setAttribute(i === "className" ? "class" : i, value[i]);
10898
      }
10899
10900
      composer.selection.insertNode(image);
10901
      if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
10902
        textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
10903
        composer.selection.insertNode(textNode);
10904
        composer.selection.setAfter(textNode);
10905
      } else {
10906
        composer.selection.setAfter(image);
10907
      }
10908
    },
10909
10910
    state: function(composer) {
10911
      var doc = composer.doc,
10912
          selectedNode,
10913
          text,
10914
          imagesInSelection;
10915
10916
      if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) {
10917
        return false;
10918
      }
10919
10920
      selectedNode = composer.selection.getSelectedNode();
10921
      if (!selectedNode) {
10922
        return false;
10923
      }
10924
10925
      if (selectedNode.nodeName === NODE_NAME) {
10926
        // This works perfectly in IE
10927
        return selectedNode;
10928
      }
10929
10930
      if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) {
10931
        return false;
10932
      }
10933
10934
      text = composer.selection.getText();
10935
      text = wysihtml5.lang.string(text).trim();
10936
      if (text) {
10937
        return false;
10938
      }
10939
10940
      imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
10941
        return node.nodeName === "IMG";
10942
      });
10943
10944
      if (imagesInSelection.length !== 1) {
10945
        return false;
10946
      }
10947
10948
      return imagesInSelection[0];
10949
    }
10950
  };
10951
})(wysihtml5);
10952
;(function(wysihtml5) {
10953
  var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
10954
10955
  wysihtml5.commands.insertLineBreak = {
10956
    exec: function(composer, command) {
10957
      if (composer.commands.support(command)) {
10958
        composer.doc.execCommand(command, false, null);
10959
        if (!wysihtml5.browser.autoScrollsToCaret()) {
10960
          composer.selection.scrollIntoView();
10961
        }
10962
      } else {
10963
        composer.commands.exec("insertHTML", LINE_BREAK);
10964
      }
10965
    },
10966
10967
    state: function() {
10968
      return false;
10969
    }
10970
  };
10971
})(wysihtml5);
10972
;wysihtml5.commands.insertOrderedList = {
10973
  exec: function(composer, command) {
10974
    wysihtml5.commands.insertList.exec(composer, command, "OL");
10975
  },
10976
10977
  state: function(composer, command) {
10978
    return wysihtml5.commands.insertList.state(composer, command, "OL");
10979
  }
10980
};
10981
;wysihtml5.commands.insertUnorderedList = {
10982
  exec: function(composer, command) {
10983
    wysihtml5.commands.insertList.exec(composer, command, "UL");
10984
  },
10985
10986
  state: function(composer, command) {
10987
    return wysihtml5.commands.insertList.state(composer, command, "UL");
10988
  }
10989
};
10990
;wysihtml5.commands.insertList = (function(wysihtml5) {
10991
10992
  var isNode = function(node, name) {
10993
    if (node && node.nodeName) {
10994
      if (typeof name === 'string') {
10995
        name = [name];
10996
      }
10997
      for (var n = name.length; n--;) {
10998
        if (node.nodeName === name[n]) {
10999
          return true;
11000
        }
11001
      }
11002
    }
11003
    return false;
11004
  };
11005
11006
  var findListEl = function(node, nodeName, composer) {
11007
    var ret = {
11008
          el: null,
11009
          other: false
11010
        };
11011
11012
    if (node) {
11013
      var parentLi = wysihtml5.dom.getParentElement(node, { nodeName: "LI" }),
11014
          otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11015
11016
      if (isNode(node, nodeName)) {
11017
        ret.el = node;
11018
      } else if (isNode(node, otherNodeName)) {
11019
        ret = {
11020
          el: node,
11021
          other: true
11022
        };
11023
      } else if (parentLi) {
11024
        if (isNode(parentLi.parentNode, nodeName)) {
11025
          ret.el = parentLi.parentNode;
11026
        } else if (isNode(parentLi.parentNode, otherNodeName)) {
11027
          ret = {
11028
            el : parentLi.parentNode,
11029
            other: true
11030
          };
11031
        }
11032
      }
11033
    }
11034
11035
    // do not count list elements outside of composer
11036
    if (ret.el && !composer.element.contains(ret.el)) {
11037
      ret.el = null;
11038
    }
11039
11040
    return ret;
11041
  };
11042
11043
  var handleSameTypeList = function(el, nodeName, composer) {
11044
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
11045
        otherLists, innerLists;
0 ignored issues
show
Unused Code introduced by
The variable otherLists seems to be never used. Consider removing it.
Loading history...
11046
    // Unwrap list
11047
    // <ul><li>foo</li><li>bar</li></ul>
11048
    // becomes:
11049
    // foo<br>bar<br>
11050
    composer.selection.executeAndRestore(function() {
11051
      var otherLists = getListsInSelection(otherNodeName, composer);
11052
      if (otherLists.length) {
11053
        for (var l = otherLists.length; l--;) {
11054
          wysihtml5.dom.renameElement(otherLists[l], nodeName.toLowerCase());
11055
        }
11056
      } else {
11057
        innerLists = getListsInSelection(['OL', 'UL'], composer);
11058
        for (var i = innerLists.length; i--;) {
11059
          wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
11060
        }
11061
        wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
11062
      }
11063
    });
11064
  };
11065
11066
  var handleOtherTypeList =  function(el, nodeName, composer) {
11067
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11068
    // Turn an ordered list into an unordered list
11069
    // <ol><li>foo</li><li>bar</li></ol>
11070
    // becomes:
11071
    // <ul><li>foo</li><li>bar</li></ul>
11072
    // Also rename other lists in selection
11073
    composer.selection.executeAndRestore(function() {
11074
      var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
11075
11076
      // All selection inner lists get renamed too
11077
      for (var l = renameLists.length; l--;) {
11078
        wysihtml5.dom.renameElement(renameLists[l], nodeName.toLowerCase());
11079
      }
11080
    });
11081
  };
11082
11083
  var getListsInSelection = function(nodeName, composer) {
11084
      var ranges = composer.selection.getOwnRanges(),
11085
          renameLists = [];
11086
11087
      for (var r = ranges.length; r--;) {
11088
        renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
11089
          return isNode(node, nodeName);
11090
        }));
11091
      }
11092
11093
      return renameLists;
11094
  };
11095
11096
  var createListFallback = function(nodeName, composer) {
11097
    // Fallback for Create list
11098
    composer.selection.executeAndRestoreRangy(function() {
11099
      var tempClassName =  "_wysihtml5-temp-" + new Date().getTime(),
11100
          tempElement = composer.selection.deblockAndSurround({
11101
            "nodeName": "div",
11102
            "className": tempClassName
11103
          }),
11104
          isEmpty, list;
11105
11106
      // This space causes new lists to never break on enter 
11107
      var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
11108
      tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
11109
      
11110
      if (tempElement) {
11111
        isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
11112
        list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname);
11113
        if (isEmpty) {
11114
          composer.selection.selectNode(list.querySelector("li"), true);
11115
        }
11116
      }
11117
    });
11118
  };
11119
11120
  return {
11121
    exec: function(composer, command, nodeName) {
11122
      var doc           = composer.doc,
11123
          cmd           = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
11124
          selectedNode  = composer.selection.getSelectedNode(),
11125
          list          = findListEl(selectedNode, nodeName, composer);
11126
11127
      if (!list.el) {
11128
        if (composer.commands.support(cmd)) {
11129
          doc.execCommand(cmd, false, null);
11130
        } else {
11131
          createListFallback(nodeName, composer);
11132
        }
11133
      } else if (list.other) {
11134
        handleOtherTypeList(list.el, nodeName, composer);
11135
      } else {
11136
        handleSameTypeList(list.el, nodeName, composer);
11137
      }
11138
    },
11139
11140
    state: function(composer, command, nodeName) {
11141
      var selectedNode = composer.selection.getSelectedNode(),
11142
          list         = findListEl(selectedNode, nodeName, composer);
11143
11144
      return (list.el && !list.other) ? list.el : false;
11145
    }
11146
  };
11147
11148
})(wysihtml5);;wysihtml5.commands.italic = {
11149
  exec: function(composer, command) {
11150
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
11151
  },
11152
11153
  state: function(composer, command) {
11154
    // element.ownerDocument.queryCommandState("italic") results:
11155
    // firefox: only <i>
11156
    // chrome:  <i>, <em>, <blockquote>, ...
11157
    // ie:      <i>, <em>
11158
    // opera:   only <i>
11159
    return wysihtml5.commands.formatInline.state(composer, command, "i");
11160
  }
11161
};
11162
;(function(wysihtml5) {
11163
  var CLASS_NAME  = "wysiwyg-text-align-center",
11164
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11165
11166
  wysihtml5.commands.justifyCenter = {
11167
    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...
11168
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11169
    },
11170
11171
    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...
11172
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11173
    }
11174
  };
11175
})(wysihtml5);
11176
;(function(wysihtml5) {
11177
  var CLASS_NAME  = "wysiwyg-text-align-left",
11178
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11179
11180
  wysihtml5.commands.justifyLeft = {
11181
    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...
11182
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11183
    },
11184
11185
    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...
11186
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11187
    }
11188
  };
11189
})(wysihtml5);
11190
;(function(wysihtml5) {
11191
  var CLASS_NAME  = "wysiwyg-text-align-right",
11192
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11193
11194
  wysihtml5.commands.justifyRight = {
11195
    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...
11196
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11197
    },
11198
11199
    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...
11200
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11201
    }
11202
  };
11203
})(wysihtml5);
11204
;(function(wysihtml5) {
11205
  var CLASS_NAME  = "wysiwyg-text-align-justify",
11206
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11207
11208
  wysihtml5.commands.justifyFull = {
11209
    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...
11210
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11211
    },
11212
11213
    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...
11214
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11215
    }
11216
  };
11217
})(wysihtml5);
11218
;(function(wysihtml5) {
11219
  var STYLE_STR  = "text-align: right;",
11220
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11221
11222
  wysihtml5.commands.alignRightStyle = {
11223
    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...
11224
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11225
    },
11226
11227
    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...
11228
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11229
    }
11230
  };
11231
})(wysihtml5);
11232
;(function(wysihtml5) {
11233
  var STYLE_STR  = "text-align: left;",
11234
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11235
11236
  wysihtml5.commands.alignLeftStyle = {
11237
    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...
11238
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11239
    },
11240
11241
    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...
11242
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11243
    }
11244
  };
11245
})(wysihtml5);
11246
;(function(wysihtml5) {
11247
  var STYLE_STR  = "text-align: center;",
11248
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11249
11250
  wysihtml5.commands.alignCenterStyle = {
11251
    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...
11252
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11253
    },
11254
11255
    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...
11256
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11257
    }
11258
  };
11259
})(wysihtml5);
11260
;wysihtml5.commands.redo = {
11261
  exec: function(composer) {
11262
    return composer.undoManager.redo();
11263
  },
11264
11265
  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...
11266
    return false;
11267
  }
11268
};
11269
;wysihtml5.commands.underline = {
11270
  exec: function(composer, command) {
11271
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "u");
11272
  },
11273
11274
  state: function(composer, command) {
11275
    return wysihtml5.commands.formatInline.state(composer, command, "u");
11276
  }
11277
};
11278
;wysihtml5.commands.undo = {
11279
  exec: function(composer) {
11280
    return composer.undoManager.undo();
11281
  },
11282
11283
  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...
11284
    return false;
11285
  }
11286
};
11287
;wysihtml5.commands.createTable = {
11288
  exec: function(composer, command, value) {
11289
      var col, row, html;
11290
      if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
11291
          if (value.tableStyle) {
11292
            html = "<table style=\"" + value.tableStyle + "\">";
11293
          } else {
11294
            html = "<table>";
11295
          }
11296
          html += "<tbody>";
11297
          for (row = 0; row < value.rows; row ++) {
11298
              html += '<tr>';
11299
              for (col = 0; col < value.cols; col ++) {
11300
                  html += "<td>&nbsp;</td>";
11301
              }
11302
              html += '</tr>';
11303
          }
11304
          html += "</tbody></table>";
11305
          composer.commands.exec("insertHTML", html);
11306
          //composer.selection.insertHTML(html);
11307
      }
11308
11309
11310
  },
11311
11312
  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...
11313
      return false;
11314
  }
11315
};
11316
;wysihtml5.commands.mergeTableCells = {
11317
  exec: function(composer, command) {
11318
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11319
          if (this.state(composer, command)) {
11320
              wysihtml5.dom.table.unmergeCell(composer.tableSelection.start);
11321
          } else {
11322
              wysihtml5.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end);
11323
          }
11324
      }
11325
  },
11326
11327
  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...
11328
      if (composer.tableSelection) {
11329
          var start = composer.tableSelection.start,
11330
              end = composer.tableSelection.end;
11331
          if (start && end && start == end &&
11332
              ((
11333
                  wysihtml5.dom.getAttribute(start, "colspan") &&
11334
                  parseInt(wysihtml5.dom.getAttribute(start, "colspan"), 10) > 1
11335
              ) || (
11336
                  wysihtml5.dom.getAttribute(start, "rowspan") &&
11337
                  parseInt(wysihtml5.dom.getAttribute(start, "rowspan"), 10) > 1
11338
              ))
11339
          ) {
11340
              return [start];
11341
          }
11342
      }
11343
      return false;
11344
  }
11345
};
11346
;wysihtml5.commands.addTableCells = {
11347
  exec: function(composer, command, value) {
11348
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11349
11350
          // switches start and end if start is bigger than end (reverse selection)
11351
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
11352
          if (value == "before" || value == "above") {
11353
              wysihtml5.dom.table.addCells(tableSelect.start, value);
11354
          } else if (value == "after" || value == "below") {
11355
              wysihtml5.dom.table.addCells(tableSelect.end, value);
11356
          }
11357
          setTimeout(function() {
11358
              composer.tableSelection.select(tableSelect.start, tableSelect.end);
11359
          },0);
11360
      }
11361
  },
11362
11363
  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...
11364
      return false;
11365
  }
11366
};
11367
;wysihtml5.commands.deleteTableCells = {
11368
  exec: function(composer, command, value) {
11369
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11370
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
11371
              idx = wysihtml5.dom.table.indexOf(tableSelect.start),
11372
              selCell,
11373
              table = composer.tableSelection.table;
11374
11375
          wysihtml5.dom.table.removeCells(tableSelect.start, value);
11376
          setTimeout(function() {
11377
              // move selection to next or previous if not present
11378
              selCell = wysihtml5.dom.table.findCell(table, idx);
11379
11380
              if (!selCell){
11381
                  if (value == "row") {
11382
                      selCell = wysihtml5.dom.table.findCell(table, {
11383
                          "row": idx.row - 1,
11384
                          "col": idx.col
11385
                      });
11386
                  }
11387
11388
                  if (value == "column") {
11389
                      selCell = wysihtml5.dom.table.findCell(table, {
11390
                          "row": idx.row,
11391
                          "col": idx.col - 1
11392
                      });
11393
                  }
11394
              }
11395
              if (selCell) {
11396
                  composer.tableSelection.select(selCell, selCell);
11397
              }
11398
          }, 0);
11399
11400
      }
11401
  },
11402
11403
  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...
11404
      return false;
11405
  }
11406
};
11407
;wysihtml5.commands.indentList = {
11408
  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...
11409
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11410
    if (listEls) {
11411
      return this.tryToPushLiLevel(listEls, composer.selection);
11412
    }
11413
    return false;
11414
  },
11415
11416
  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...
11417
      return false;
11418
  },
11419
11420
  tryToPushLiLevel: function(liNodes, selection) {
11421
    var listTag, list, prevLi, liNode, prevLiList,
11422
        found = false;
11423
11424
    selection.executeAndRestoreRangy(function() {
11425
11426
      for (var i = liNodes.length; i--;) {
11427
        liNode = liNodes[i];
11428
        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 11427. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11429
        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 11428. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11430
        prevLi = wysihtml5.dom.domNode(liNode).prev({nodeTypes: [wysihtml5.ELEMENT_NODE]});
11431
        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 11430. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11432
11433
        if (prevLi) {
11434
          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 11431. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11435
            prevLiList.appendChild(liNode);
11436
          } else {
11437
            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 11429. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11438
            prevLi.appendChild(list);
11439
          }
11440
          found = true;
11441
        }
11442
      }
11443
11444
    });
11445
    return found;
11446
  }
11447
};
11448
;wysihtml5.commands.outdentList = {
11449
  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...
11450
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11451
    if (listEls) {
11452
      return this.tryToPullLiLevel(listEls, composer);
11453
    }
11454
    return false;
11455
  },
11456
11457
  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...
11458
      return false;
11459
  },
11460
11461
  tryToPullLiLevel: function(liNodes, composer) {
11462
    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...
11463
        found = false,
11464
        that = this;
11465
11466
    composer.selection.executeAndRestoreRangy(function() {
11467
11468
      for (var i = liNodes.length; i--;) {
11469
        liNode = liNodes[i];
11470
        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 11469. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11471
          listNode = liNode.parentNode;
11472
11473
          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 11471. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11474
            found = true;
11475
11476
            outerListNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['OL', 'UL']}, false, composer.element);
11477
            outerLiNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['LI']}, false, composer.element);
11478
11479
            if (outerListNode && outerLiNode) {
0 ignored issues
show
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 11476. 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 outerLiNode is changed as part of the for loop for example by wysihtml5.dom.getParentE...alse, composer.element) on line 11477. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11480
11481
              if (liNode.nextSibling) {
11482
                afterList = that.getAfterList(listNode, liNode);
11483
                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 11482. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11484
              }
11485
              outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
11486
11487
            } else {
11488
11489
              if (liNode.nextSibling) {
11490
                afterList = that.getAfterList(listNode, liNode);
11491
                liNode.appendChild(afterList);
11492
              }
11493
11494
              for (var j = liNode.childNodes.length; j--;) {
11495
                listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
11496
              }
11497
11498
              listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
11499
              liNode.parentNode.removeChild(liNode);
11500
11501
            }
11502
11503
            // cleanup
11504
            if (listNode.childNodes.length === 0) {
11505
                listNode.parentNode.removeChild(listNode);
11506
            }
11507
          }
11508
        }
11509
      }
11510
11511
    });
11512
    return found;
11513
  },
11514
11515
  getAfterList: function(listNode, liNode) {
11516
    var nodeName = listNode.nodeName,
11517
        newList = document.createElement(nodeName);
11518
11519
    while (liNode.nextSibling) {
11520
      newList.appendChild(liNode.nextSibling);
11521
    }
11522
    return newList;
11523
  }
11524
11525
};;/**
11526
 * Undo Manager for wysihtml5
11527
 * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
11528
 */
11529
(function(wysihtml5) {
11530
  var Z_KEY               = 90,
11531
      Y_KEY               = 89,
11532
      BACKSPACE_KEY       = 8,
11533
      DELETE_KEY          = 46,
11534
      MAX_HISTORY_ENTRIES = 25,
11535
      DATA_ATTR_NODE      = "data-wysihtml5-selection-node",
11536
      DATA_ATTR_OFFSET    = "data-wysihtml5-selection-offset",
11537
      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...
11538
      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...
11539
      dom                 = wysihtml5.dom;
11540
11541
  function cleanTempElements(doc) {
0 ignored issues
show
introduced by
The function cleanTempElements does not seem to be used and can be removed.
Loading history...
11542
    var tempElement;
11543
    while (tempElement = doc.querySelector("._wysihtml5-temp")) {
11544
      tempElement.parentNode.removeChild(tempElement);
11545
    }
11546
  }
11547
11548
  wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend(
11549
    /** @scope wysihtml5.UndoManager.prototype */ {
11550
    constructor: function(editor) {
11551
      this.editor = editor;
11552
      this.composer = editor.composer;
11553
      this.element = this.composer.element;
11554
11555
      this.position = 0;
11556
      this.historyStr = [];
11557
      this.historyDom = [];
11558
11559
      this.transact();
11560
11561
      this._observe();
11562
    },
11563
11564
    _observe: function() {
11565
      var that      = this,
11566
          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...
11567
          lastKey;
11568
11569
      // Catch CTRL+Z and CTRL+Y
11570
      dom.observe(this.element, "keydown", function(event) {
11571
        if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
11572
          return;
11573
        }
11574
11575
        var keyCode = event.keyCode,
11576
            isUndo = keyCode === Z_KEY && !event.shiftKey,
11577
            isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
11578
11579
        if (isUndo) {
11580
          that.undo();
11581
          event.preventDefault();
11582
        } else if (isRedo) {
11583
          that.redo();
11584
          event.preventDefault();
11585
        }
11586
      });
11587
11588
      // Catch delete and backspace
11589
      dom.observe(this.element, "keydown", function(event) {
11590
        var keyCode = event.keyCode;
11591
        if (keyCode === lastKey) {
11592
          return;
11593
        }
11594
11595
        lastKey = keyCode;
11596
11597
        if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
11598
          that.transact();
11599
        }
11600
      });
11601
11602
      this.editor
11603
        .on("newword:composer", function() {
11604
          that.transact();
11605
        })
11606
11607
        .on("beforecommand:composer", function() {
11608
          that.transact();
11609
        });
11610
    },
11611
11612
    transact: function() {
11613
      var previousHtml      = this.historyStr[this.position - 1],
11614
          currentHtml       = this.composer.getValue(false, false),
11615
          composerIsVisible   = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
11616
          range, node, offset, element, position;
11617
11618
      if (currentHtml === previousHtml) {
11619
        return;
11620
      }
11621
11622
      var length = this.historyStr.length = this.historyDom.length = this.position;
11623
      if (length > MAX_HISTORY_ENTRIES) {
11624
        this.historyStr.shift();
11625
        this.historyDom.shift();
11626
        this.position--;
11627
      }
11628
11629
      this.position++;
11630
11631
      if (composerIsVisible) {
11632
        // Do not start saving selection if composer is not visible
11633
        range   = this.composer.selection.getRange();
11634
        node    = (range && range.startContainer) ? range.startContainer : this.element;
11635
        offset  = (range && range.startOffset) ? range.startOffset : 0;
11636
11637
        if (node.nodeType === wysihtml5.ELEMENT_NODE) {
11638
          element = node;
11639
        } else {
11640
          element  = node.parentNode;
11641
          position = this.getChildNodeIndex(element, node);
11642
        }
11643
11644
        element.setAttribute(DATA_ATTR_OFFSET, offset);
11645
        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...
11646
          element.setAttribute(DATA_ATTR_NODE, position);
11647
        }
11648
      }
11649
11650
      var clone = this.element.cloneNode(!!currentHtml);
11651
      this.historyDom.push(clone);
11652
      this.historyStr.push(currentHtml);
11653
11654
      if (element) {
11655
        element.removeAttribute(DATA_ATTR_OFFSET);
11656
        element.removeAttribute(DATA_ATTR_NODE);
11657
      }
11658
11659
    },
11660
11661
    undo: function() {
11662
      this.transact();
11663
11664
      if (!this.undoPossible()) {
11665
        return;
11666
      }
11667
11668
      this.set(this.historyDom[--this.position - 1]);
11669
      this.editor.fire("undo:composer");
11670
    },
11671
11672
    redo: function() {
11673
      if (!this.redoPossible()) {
11674
        return;
11675
      }
11676
11677
      this.set(this.historyDom[++this.position - 1]);
11678
      this.editor.fire("redo:composer");
11679
    },
11680
11681
    undoPossible: function() {
11682
      return this.position > 1;
11683
    },
11684
11685
    redoPossible: function() {
11686
      return this.position < this.historyStr.length;
11687
    },
11688
11689
    set: function(historyEntry) {
11690
      this.element.innerHTML = "";
11691
11692
      var i = 0,
11693
          childNodes = historyEntry.childNodes,
11694
          length = historyEntry.childNodes.length;
11695
11696
      for (; i<length; i++) {
11697
        this.element.appendChild(childNodes[i].cloneNode(true));
11698
      }
11699
11700
      // Restore selection
11701
      var offset,
11702
          node,
11703
          position;
11704
11705
      if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
11706
        offset    = historyEntry.getAttribute(DATA_ATTR_OFFSET);
11707
        position  = historyEntry.getAttribute(DATA_ATTR_NODE);
11708
        node      = this.element;
11709
      } else {
11710
        node      = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
11711
        offset    = node.getAttribute(DATA_ATTR_OFFSET);
11712
        position  = node.getAttribute(DATA_ATTR_NODE);
11713
        node.removeAttribute(DATA_ATTR_OFFSET);
11714
        node.removeAttribute(DATA_ATTR_NODE);
11715
      }
11716
11717
      if (position !== null) {
11718
        node = this.getChildNodeByIndex(node, +position);
11719
      }
11720
11721
      this.composer.selection.set(node, offset);
11722
    },
11723
11724
    getChildNodeIndex: function(parent, child) {
11725
      var i           = 0,
11726
          childNodes  = parent.childNodes,
11727
          length      = childNodes.length;
11728
      for (; i<length; i++) {
11729
        if (childNodes[i] === child) {
11730
          return i;
11731
        }
11732
      }
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...
11733
    },
11734
11735
    getChildNodeByIndex: function(parent, index) {
11736
      return parent.childNodes[index];
11737
    }
11738
  });
11739
})(wysihtml5);
11740
;/**
11741
 * TODO: the following methods still need unit test coverage
11742
 */
11743
wysihtml5.views.View = Base.extend(
11744
  /** @scope wysihtml5.views.View.prototype */ {
11745
  constructor: function(parent, textareaElement, config) {
11746
    this.parent   = parent;
11747
    this.element  = textareaElement;
11748
    this.config   = config;
11749
    if (!this.config.noTextarea) {
11750
        this._observeViewChange();
11751
    }
11752
  },
11753
11754
  _observeViewChange: function() {
11755
    var that = this;
11756
    this.parent.on("beforeload", function() {
11757
      that.parent.on("change_view", function(view) {
11758
        if (view === that.name) {
11759
          that.parent.currentView = that;
11760
          that.show();
11761
          // Using tiny delay here to make sure that the placeholder is set before focusing
11762
          setTimeout(function() { that.focus(); }, 0);
11763
        } else {
11764
          that.hide();
11765
        }
11766
      });
11767
    });
11768
  },
11769
11770
  focus: function() {
11771
    if (this.element.ownerDocument.querySelector(":focus") === this.element) {
11772
      return;
11773
    }
11774
11775
    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...
11776
  },
11777
11778
  hide: function() {
11779
    this.element.style.display = "none";
11780
  },
11781
11782
  show: function() {
11783
    this.element.style.display = "";
11784
  },
11785
11786
  disable: function() {
11787
    this.element.setAttribute("disabled", "disabled");
11788
  },
11789
11790
  enable: function() {
11791
    this.element.removeAttribute("disabled");
11792
  }
11793
});
11794
;(function(wysihtml5) {
11795
  var dom       = wysihtml5.dom,
11796
      browser   = wysihtml5.browser;
11797
11798
  wysihtml5.views.Composer = wysihtml5.views.View.extend(
11799
    /** @scope wysihtml5.views.Composer.prototype */ {
11800
    name: "composer",
11801
11802
    // Needed for firefox in order to display a proper caret in an empty contentEditable
11803
    CARET_HACK: "<br>",
11804
11805
    constructor: function(parent, editableElement, config) {
11806
      this.base(parent, editableElement, config);
11807
      if (!this.config.noTextarea) {
11808
          this.textarea = this.parent.textarea;
11809
      } else {
11810
          this.editableArea = editableElement;
11811
      }
11812
      if (this.config.contentEditableMode) {
11813
          this._initContentEditableArea();
11814
      } else {
11815
          this._initSandbox();
11816
      }
11817
    },
11818
11819
    clear: function() {
11820
      this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
11821
    },
11822
11823
    getValue: function(parse, clearInternals) {
11824
      var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
11825
      if (parse !== false) {
11826
        value = this.parent.parse(value, (clearInternals === false) ? false : true);
11827
      }
11828
11829
      return value;
11830
    },
11831
11832
    setValue: function(html, parse) {
11833
      if (parse) {
11834
        html = this.parent.parse(html);
11835
      }
11836
11837
      try {
11838
        this.element.innerHTML = html;
11839
      } catch (e) {
11840
        this.element.innerText = html;
11841
      }
11842
    },
11843
11844
    cleanUp: function() {
11845
        this.parent.parse(this.element);
11846
    },
11847
11848
    show: function() {
11849
      this.editableArea.style.display = this._displayStyle || "";
11850
11851
      if (!this.config.noTextarea && !this.textarea.element.disabled) {
11852
        // Firefox needs this, otherwise contentEditable becomes uneditable
11853
        this.disable();
11854
        this.enable();
11855
      }
11856
    },
11857
11858
    hide: function() {
11859
      this._displayStyle = dom.getStyle("display").from(this.editableArea);
11860
      if (this._displayStyle === "none") {
11861
        this._displayStyle = null;
11862
      }
11863
      this.editableArea.style.display = "none";
11864
    },
11865
11866
    disable: function() {
11867
      this.parent.fire("disable:composer");
11868
      this.element.removeAttribute("contentEditable");
11869
    },
11870
11871
    enable: function() {
11872
      this.parent.fire("enable:composer");
11873
      this.element.setAttribute("contentEditable", "true");
11874
    },
11875
11876
    focus: function(setToEnd) {
11877
      // IE 8 fires the focus event after .focus()
11878
      // This is needed by our simulate_placeholder.js to work
11879
      // therefore we clear it ourselves this time
11880
      if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
11881
        this.clear();
11882
      }
11883
11884
      this.base();
11885
11886
      var lastChild = this.element.lastChild;
11887
      if (setToEnd && lastChild && this.selection) {
11888
        if (lastChild.nodeName === "BR") {
11889
          this.selection.setBefore(this.element.lastChild);
11890
        } else {
11891
          this.selection.setAfter(this.element.lastChild);
11892
        }
11893
      }
11894
    },
11895
11896
    getTextContent: function() {
11897
      return dom.getTextContent(this.element);
11898
    },
11899
11900
    hasPlaceholderSet: function() {
11901
      return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
11902
    },
11903
11904
    isEmpty: function() {
11905
      var innerHTML = this.element.innerHTML.toLowerCase();
11906
      return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML)  ||
11907
             innerHTML === ""            ||
11908
             innerHTML === "<br>"        ||
11909
             innerHTML === "<p></p>"     ||
11910
             innerHTML === "<p><br></p>" ||
11911
             this.hasPlaceholderSet();
11912
    },
11913
11914
    _initContentEditableArea: function() {
11915
        var that = this;
11916
11917
        if (this.config.noTextarea) {
11918
            this.sandbox = new dom.ContentEditableArea(function() {
11919
                that._create();
11920
            }, {}, this.editableArea);
11921
        } else {
11922
            this.sandbox = new dom.ContentEditableArea(function() {
11923
                that._create();
11924
            });
11925
            this.editableArea = this.sandbox.getContentEditable();
11926
            dom.insert(this.editableArea).after(this.textarea.element);
11927
            this._createWysiwygFormField();
11928
        }
11929
    },
11930
11931
    _initSandbox: function() {
11932
      var that = this;
11933
11934
      this.sandbox = new dom.Sandbox(function() {
11935
        that._create();
11936
      }, {
11937
        stylesheets:  this.config.stylesheets
11938
      });
11939
      this.editableArea  = this.sandbox.getIframe();
11940
11941
      var textareaElement = this.textarea.element;
11942
      dom.insert(this.editableArea).after(textareaElement);
11943
11944
      this._createWysiwygFormField();
11945
    },
11946
11947
    // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
11948
    _createWysiwygFormField: function() {
11949
        if (this.textarea.element.form) {
11950
          var hiddenField = document.createElement("input");
11951
          hiddenField.type   = "hidden";
11952
          hiddenField.name   = "_wysihtml5_mode";
11953
          hiddenField.value  = 1;
11954
          dom.insert(hiddenField).after(this.textarea.element);
11955
        }
11956
    },
11957
11958
    _create: function() {
11959
      var that = this;
11960
      this.doc                = this.sandbox.getDocument();
11961
      this.element            = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
11962
      if (!this.config.noTextarea) {
11963
          this.textarea           = this.parent.textarea;
11964
          this.element.innerHTML  = this.textarea.getValue(true, false);
11965
      } else {
11966
          this.cleanUp(); // cleans contenteditable on initiation as it may contain html
11967
      }
11968
11969
      // Make sure our selection handler is ready
11970
      this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname);
11971
11972
      // Make sure commands dispatcher is ready
11973
      this.commands  = new wysihtml5.Commands(this.parent);
11974
11975
      if (!this.config.noTextarea) {
11976
          dom.copyAttributes([
11977
              "className", "spellcheck", "title", "lang", "dir", "accessKey"
11978
          ]).from(this.textarea.element).to(this.element);
11979
      }
11980
11981
      dom.addClass(this.element, this.config.composerClassName);
11982
      //
11983
      // Make the editor look like the original textarea, by syncing styles
11984
      if (this.config.style && !this.config.contentEditableMode) {
11985
        this.style();
11986
      }
11987
11988
      this.observe();
11989
11990
      var name = this.config.name;
11991
      if (name) {
11992
        dom.addClass(this.element, name);
11993
        if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
11994
      }
11995
11996
      this.enable();
11997
11998
      if (!this.config.noTextarea && this.textarea.element.disabled) {
11999
        this.disable();
12000
      }
12001
12002
      // Simulate html5 placeholder attribute on contentEditable element
12003
      var placeholderText = typeof(this.config.placeholder) === "string"
12004
        ? this.config.placeholder
12005
        : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
12006
      if (placeholderText) {
12007
        dom.simulatePlaceholder(this.parent, this, placeholderText);
12008
      }
12009
12010
      // Make sure that the browser avoids using inline styles whenever possible
12011
      this.commands.exec("styleWithCSS", false);
12012
12013
      this._initAutoLinking();
12014
      this._initObjectResizing();
12015
      this._initUndoManager();
12016
      this._initLineBreaking();
12017
12018
      // Simulate html5 autofocus on contentEditable element
12019
      // This doesn't work on IOS (5.1.1)
12020
      if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
12021
        setTimeout(function() { that.focus(true); }, 100);
12022
      }
12023
12024
      // IE sometimes leaves a single paragraph, which can't be removed by the user
12025
      if (!browser.clearsContentEditableCorrectly()) {
12026
        wysihtml5.quirks.ensureProperClearing(this);
12027
      }
12028
12029
      // Set up a sync that makes sure that textarea and editor have the same content
12030
      if (this.initSync && this.config.sync) {
12031
        this.initSync();
12032
      }
12033
12034
      // Okay hide the textarea, we are ready to go
12035
      if (!this.config.noTextarea) { this.textarea.hide(); }
12036
12037
      // Fire global (before-)load event
12038
      this.parent.fire("beforeload").fire("load");
12039
    },
12040
12041
    _initAutoLinking: function() {
12042
      var that                           = this,
12043
          supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
12044
          supportsAutoLinking            = browser.doesAutoLinkingInContentEditable();
12045
      if (supportsDisablingOfAutoLinking) {
12046
        this.commands.exec("autoUrlDetect", false);
12047
      }
12048
12049
      if (!this.config.autoLink) {
12050
        return;
12051
      }
12052
12053
      // Only do the auto linking by ourselves when the browser doesn't support auto linking
12054
      // OR when he supports auto linking but we were able to turn it off (IE9+)
12055
      if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
12056
        this.parent.on("newword:composer", function() {
12057
          if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
12058
            that.selection.executeAndRestore(function(startContainer, endContainer) {
12059
              var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
12060
                  isInUneditable = false;
12061
12062
              for (var i = uneditables.length; i--;) {
12063
                if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
12064
                  isInUneditable = true;
12065
                }
12066
              }
12067
12068
              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...
12069
            });
12070
          }
12071
        });
12072
12073
        dom.observe(this.element, "blur", function() {
12074
          dom.autoLink(that.element, [that.config.uneditableContainerClassname]);
12075
        });
12076
      }
12077
12078
      // Assuming we have the following:
12079
      //  <a href="http://www.google.de">http://www.google.de</a>
12080
      // If a user now changes the url in the innerHTML we want to make sure that
12081
      // it's synchronized with the href attribute (as long as the innerHTML is still a url)
12082
      var // Use a live NodeList to check whether there are any links in the document
12083
          links           = this.sandbox.getDocument().getElementsByTagName("a"),
12084
          // The autoLink helper method reveals a reg exp to detect correct urls
12085
          urlRegExp       = dom.autoLink.URL_REG_EXP,
12086
          getTextContent  = function(element) {
12087
            var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim();
12088
            if (textContent.substr(0, 4) === "www.") {
12089
              textContent = "http://" + textContent;
12090
            }
12091
            return textContent;
12092
          };
12093
12094
      dom.observe(this.element, "keydown", function(event) {
12095
        if (!links.length) {
12096
          return;
12097
        }
12098
12099
        var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
12100
            link         = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
12101
            textContent;
12102
12103
        if (!link) {
12104
          return;
12105
        }
12106
12107
        textContent = getTextContent(link);
12108
        // keydown is fired before the actual content is changed
12109
        // therefore we set a timeout to change the href
12110
        setTimeout(function() {
12111
          var newTextContent = getTextContent(link);
12112
          if (newTextContent === textContent) {
12113
            return;
12114
          }
12115
12116
          // Only set href when new href looks like a valid url
12117
          if (newTextContent.match(urlRegExp)) {
12118
            link.setAttribute("href", newTextContent);
12119
          }
12120
        }, 0);
12121
      });
12122
    },
12123
12124
    _initObjectResizing: function() {
12125
      this.commands.exec("enableObjectResizing", true);
12126
12127
      // IE sets inline styles after resizing objects
12128
      // The following lines make sure that the width/height css properties
12129
      // are copied over to the width/height attributes
12130
      if (browser.supportsEvent("resizeend")) {
12131
        var properties        = ["width", "height"],
12132
            propertiesLength  = properties.length,
12133
            element           = this.element;
12134
12135
        dom.observe(element, "resizeend", function(event) {
12136
          var target = event.target || event.srcElement,
12137
              style  = target.style,
12138
              i      = 0,
12139
              property;
12140
12141
          if (target.nodeName !== "IMG") {
12142
            return;
12143
          }
12144
12145
          for (; i<propertiesLength; i++) {
12146
            property = properties[i];
12147
            if (style[property]) {
12148
              target.setAttribute(property, parseInt(style[property], 10));
12149
              style[property] = "";
12150
            }
12151
          }
12152
12153
          // After resizing IE sometimes forgets to remove the old resize handles
12154
          wysihtml5.quirks.redraw(element);
12155
        });
12156
      }
12157
    },
12158
12159
    _initUndoManager: function() {
12160
      this.undoManager = new wysihtml5.UndoManager(this.parent);
12161
    },
12162
12163
    _initLineBreaking: function() {
12164
      var that                              = this,
12165
          USE_NATIVE_LINE_BREAK_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
12166
          LIST_TAGS                         = ["UL", "OL", "MENU"];
12167
12168
      function adjust(selectedNode) {
12169
        var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
12170
        if (parentElement && dom.contains(that.element, parentElement)) {
12171
          that.selection.executeAndRestore(function() {
12172
            if (that.config.useLineBreaks) {
12173
              dom.replaceWithChildNodes(parentElement);
12174
            } else if (parentElement.nodeName !== "P") {
12175
              dom.renameElement(parentElement, "p");
12176
            }
12177
          });
12178
        }
12179
      }
12180
12181
      if (!this.config.useLineBreaks) {
12182
        dom.observe(this.element, ["focus", "keydown"], function() {
12183
          if (that.isEmpty()) {
12184
            var paragraph = that.doc.createElement("P");
12185
            that.element.innerHTML = "";
12186
            that.element.appendChild(paragraph);
12187
            if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
12188
              paragraph.innerHTML = "<br>";
12189
              that.selection.setBefore(paragraph.firstChild);
12190
            } else {
12191
              that.selection.selectNode(paragraph, true);
12192
            }
12193
          }
12194
        });
12195
      }
12196
12197
      // Under certain circumstances Chrome + Safari create nested <p> or <hX> tags after paste
12198
      // Inserting an invisible white space in front of it fixes the issue
12199
      // This is too hacky and causes selection not to replace content on paste in chrome
12200
     /* if (browser.createsNestedInvalidMarkupAfterPaste()) {
12201
        dom.observe(this.element, "paste", function(event) {
12202
          var invisibleSpace = that.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
12203
          that.selection.insertNode(invisibleSpace);
12204
        });
12205
      }*/
12206
12207
12208
      dom.observe(this.element, "keydown", function(event) {
12209
        var keyCode = event.keyCode;
12210
12211
        if (event.shiftKey) {
12212
          return;
12213
        }
12214
12215
        if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
12216
          return;
12217
        }
12218
        var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { nodeName: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
12219
        if (blockElement) {
12220
          setTimeout(function() {
12221
            // Unwrap paragraph after leaving a list or a H1-6
12222
            var selectedNode = that.selection.getSelectedNode(),
12223
                list;
12224
12225
            if (blockElement.nodeName === "LI") {
12226
              if (!selectedNode) {
12227
                return;
12228
              }
12229
12230
              list = dom.getParentElement(selectedNode, { nodeName: LIST_TAGS }, 2);
12231
12232
              if (!list) {
12233
                adjust(selectedNode);
12234
              }
12235
            }
12236
12237
            if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
12238
              adjust(selectedNode);
12239
            }
12240
          }, 0);
12241
          return;
12242
        }
12243
12244
        if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
12245
          event.preventDefault();
12246
          that.commands.exec("insertLineBreak");
12247
12248
        }
12249
      });
12250
    }
12251
  });
12252
})(wysihtml5);
12253
;(function(wysihtml5) {
12254
  var dom             = wysihtml5.dom,
12255
      doc             = document,
12256
      win             = window,
12257
      HOST_TEMPLATE   = doc.createElement("div"),
12258
      /**
12259
       * Styles to copy from textarea to the composer element
12260
       */
12261
      TEXT_FORMATTING = [
12262
        "background-color",
12263
        "color", "cursor",
12264
        "font-family", "font-size", "font-style", "font-variant", "font-weight",
12265
        "line-height", "letter-spacing",
12266
        "text-align", "text-decoration", "text-indent", "text-rendering",
12267
        "word-break", "word-wrap", "word-spacing"
12268
      ],
12269
      /**
12270
       * Styles to copy from textarea to the iframe
12271
       */
12272
      BOX_FORMATTING = [
12273
        "background-color",
12274
        "border-collapse",
12275
        "border-bottom-color", "border-bottom-style", "border-bottom-width",
12276
        "border-left-color", "border-left-style", "border-left-width",
12277
        "border-right-color", "border-right-style", "border-right-width",
12278
        "border-top-color", "border-top-style", "border-top-width",
12279
        "clear", "display", "float",
12280
        "margin-bottom", "margin-left", "margin-right", "margin-top",
12281
        "outline-color", "outline-offset", "outline-width", "outline-style",
12282
        "padding-left", "padding-right", "padding-top", "padding-bottom",
12283
        "position", "top", "left", "right", "bottom", "z-index",
12284
        "vertical-align", "text-align",
12285
        "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
12286
        "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
12287
        "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
12288
        "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
12289
        "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
12290
        "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
12291
        "width", "height"
12292
      ],
12293
      ADDITIONAL_CSS_RULES = [
12294
        "html                 { height: 100%; }",
12295
        "body                 { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
12296
        "body > p:first-child { margin-top: 0; }",
12297
        "._wysihtml5-temp     { display: none; }",
12298
        wysihtml5.browser.isGecko ?
12299
          "body.placeholder { color: graytext !important; }" :
12300
          "body.placeholder { color: #a9a9a9 !important; }",
12301
        // Ensure that user see's broken images and can delete them
12302
        "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
12303
      ];
12304
12305
  /**
12306
   * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
12307
   * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
12308
   *
12309
   * Other browsers need a more hacky way: (pssst don't tell my mama)
12310
   * In order to prevent the element being scrolled into view when focusing it, we simply
12311
   * move it out of the scrollable area, focus it, and reset it's position
12312
   */
12313
  var focusWithoutScrolling = function(element) {
12314
    if (element.setActive) {
12315
      // Following line could cause a js error when the textarea is invisible
12316
      // See https://github.com/xing/wysihtml5/issues/9
12317
      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...
12318
    } else {
12319
      var elementStyle = element.style,
12320
          originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
12321
          originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
12322
          originalStyles = {
12323
            position:         elementStyle.position,
12324
            top:              elementStyle.top,
12325
            left:             elementStyle.left,
12326
            WebkitUserSelect: elementStyle.WebkitUserSelect
12327
          };
12328
12329
      dom.setStyles({
12330
        position:         "absolute",
12331
        top:              "-99999px",
12332
        left:             "-99999px",
12333
        // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
12334
        WebkitUserSelect: "none"
12335
      }).on(element);
12336
12337
      element.focus();
12338
12339
      dom.setStyles(originalStyles).on(element);
12340
12341
      if (win.scrollTo) {
12342
        // Some browser extensions unset this method to prevent annoyances
12343
        // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
12344
        // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
12345
        win.scrollTo(originalScrollLeft, originalScrollTop);
12346
      }
12347
    }
12348
  };
12349
12350
12351
  wysihtml5.views.Composer.prototype.style = function() {
12352
    var that                  = this,
12353
        originalActiveElement = doc.querySelector(":focus"),
12354
        textareaElement       = this.textarea.element,
12355
        hasPlaceholder        = textareaElement.hasAttribute("placeholder"),
12356
        originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder"),
12357
        originalDisplayValue  = textareaElement.style.display,
12358
        originalDisabled      = textareaElement.disabled,
12359
        displayValueForCopying;
12360
12361
    this.focusStylesHost      = HOST_TEMPLATE.cloneNode(false);
12362
    this.blurStylesHost       = HOST_TEMPLATE.cloneNode(false);
12363
    this.disabledStylesHost   = HOST_TEMPLATE.cloneNode(false);
12364
12365
    // Remove placeholder before copying (as the placeholder has an affect on the computed style)
12366
    if (hasPlaceholder) {
12367
      textareaElement.removeAttribute("placeholder");
12368
    }
12369
12370
    if (textareaElement === originalActiveElement) {
12371
      textareaElement.blur();
12372
    }
12373
12374
    // enable for copying styles
12375
    textareaElement.disabled = false;
12376
12377
    // set textarea to display="none" to get cascaded styles via getComputedStyle
12378
    textareaElement.style.display = displayValueForCopying = "none";
12379
12380
    if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
12381
        (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
12382
      textareaElement.style.display = displayValueForCopying = originalDisplayValue;
12383
    }
12384
12385
    // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
12386
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
12387
12388
    // --------- editor styles ---------
12389
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
12390
12391
    // --------- apply standard rules ---------
12392
    dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
12393
12394
    // --------- :disabled styles ---------
12395
    textareaElement.disabled = true;
12396
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12397
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12398
    textareaElement.disabled = originalDisabled;
12399
12400
    // --------- :focus styles ---------
12401
    textareaElement.style.display = originalDisplayValue;
12402
    focusWithoutScrolling(textareaElement);
12403
    textareaElement.style.display = displayValueForCopying;
12404
12405
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12406
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12407
12408
    // reset textarea
12409
    textareaElement.style.display = originalDisplayValue;
12410
12411
    dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
12412
12413
    // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
12414
    // this is needed for when the change_view event is fired where the iframe is hidden and then
12415
    // the blur event fires and re-displays it
12416
    var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]);
12417
12418
    // --------- restore focus ---------
12419
    if (originalActiveElement) {
12420
      originalActiveElement.focus();
12421
    } else {
12422
      textareaElement.blur();
12423
    }
12424
12425
    // --------- restore placeholder ---------
12426
    if (hasPlaceholder) {
12427
      textareaElement.setAttribute("placeholder", originalPlaceholder);
12428
    }
12429
12430
    // --------- Sync focus/blur styles ---------
12431
    this.parent.on("focus:composer", function() {
12432
      dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
12433
      dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
12434
    });
12435
12436
    this.parent.on("blur:composer", function() {
12437
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12438
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12439
    });
12440
12441
    this.parent.observe("disable:composer", function() {
12442
      dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
12443
      dom.copyStyles(TEXT_FORMATTING)     .from(that.disabledStylesHost).to(that.element);
12444
    });
12445
12446
    this.parent.observe("enable:composer", function() {
12447
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12448
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12449
    });
12450
12451
    return this;
12452
  };
12453
})(wysihtml5);
12454
;/**
12455
 * Taking care of events
12456
 *  - Simulating 'change' event on contentEditable element
12457
 *  - Handling drag & drop logic
12458
 *  - Catch paste events
12459
 *  - Dispatch proprietary newword:composer event
12460
 *  - Keyboard shortcuts
12461
 */
12462
(function(wysihtml5) {
12463
  var dom       = wysihtml5.dom,
12464
      browser   = wysihtml5.browser,
12465
      /**
12466
       * Map keyCodes to query commands
12467
       */
12468
      shortcuts = {
12469
        "66": "bold",     // B
12470
        "73": "italic",   // I
12471
        "85": "underline" // U
12472
      };
12473
12474
  var deleteAroundEditable = function(selection, uneditable, element) {
12475
    // merge node with previous node from uneditable
12476
    var prevNode = selection.getPreviousNode(uneditable, true),
12477
        curNode = selection.getSelectedNode();
12478
12479
    if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
12480
    if (prevNode) {
12481
      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...
12482
        var first = curNode.firstChild;
12483
12484
        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...
12485
          while (curNode.firstChild) {
12486
            prevNode.appendChild(curNode.firstChild);
12487
          }
12488
        } else {
12489
          while (curNode.firstChild) {
12490
            uneditable.parentNode.insertBefore(curNode.firstChild, uneditable);
12491
          }
12492
        }
12493
        if (curNode.parentNode) {
12494
          curNode.parentNode.removeChild(curNode);
12495
        }
12496
        selection.setBefore(first);
12497
      } else {
12498
        if (prevNode.nodeType == 1) {
12499
          prevNode.appendChild(curNode);
12500
        } else {
12501
          uneditable.parentNode.insertBefore(curNode, uneditable);
12502
        }
12503
        selection.setBefore(curNode);
12504
      }
12505
    }
12506
  };
12507
12508
  var handleDeleteKeyPress = function(event, selection, element, composer) {
12509
    if (selection.isCollapsed()) {
12510
      if (selection.caretIsInTheBeginnig('LI')) {
12511
        event.preventDefault();
12512
        composer.commands.exec('outdentList');
12513
      } else if (selection.caretIsInTheBeginnig()) {
12514
        event.preventDefault();
12515
      } else {
12516
12517
        if (selection.caretIsFirstInSelection() &&
12518
            selection.getPreviousNode() &&
12519
            selection.getPreviousNode().nodeName &&
12520
            (/^H\d$/gi).test(selection.getPreviousNode().nodeName)
12521
        ) {
12522
          var prevNode = selection.getPreviousNode();
12523
          event.preventDefault();
12524
          if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
12525
            // heading is empty
12526
            prevNode.parentNode.removeChild(prevNode);
12527
          } else {
12528
            var range = prevNode.ownerDocument.createRange();
12529
            range.selectNodeContents(prevNode);
12530
            range.collapse(false);
12531
            selection.setSelection(range);
12532
          }
12533
        }
12534
12535
        var beforeUneditable = selection.caretIsBeforeUneditable();
12536
        // Do a special delete if caret would delete uneditable
12537
        if (beforeUneditable) {
12538
          event.preventDefault();
12539
          deleteAroundEditable(selection, beforeUneditable, element);
12540
        }
12541
      }
12542
    } else {
12543
      if (selection.containsUneditable()) {
12544
        event.preventDefault();
12545
        selection.deleteContents();
12546
      }
12547
    }
12548
  };
12549
12550
  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...
12551
    if (!composer.selection.isCollapsed()) {
12552
      composer.selection.deleteContents();
12553
    } else if (composer.selection.caretIsInTheBeginnig('LI')) {
12554
      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...
12555
    }
12556
12557
    // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
12558
    composer.commands.exec("insertHTML", "&emsp;");
12559
  };
12560
12561
  wysihtml5.views.Composer.prototype.observe = function() {
12562
    var that                = this,
12563
        state               = this.getValue(false, false),
12564
        container           = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
12565
        element             = this.element,
12566
        focusBlurElement    = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
12567
        pasteEvents         = ["drop", "paste"],
12568
        interactionEvents   = ["drop", "paste", "mouseup", "focus", "keyup"];
12569
12570
    // --------- destroy:composer event ---------
12571
    dom.observe(container, "DOMNodeRemoved", function() {
12572
      clearInterval(domNodeRemovedInterval);
12573
      that.parent.fire("destroy:composer");
12574
    });
12575
12576
    // DOMNodeRemoved event is not supported in IE 8
12577
    if (!browser.supportsMutationEvents()) {
12578
        var domNodeRemovedInterval = setInterval(function() {
12579
          if (!dom.contains(document.documentElement, container)) {
12580
            clearInterval(domNodeRemovedInterval);
12581
            that.parent.fire("destroy:composer");
12582
          }
12583
        }, 250);
12584
    }
12585
12586
    // --------- User interaction tracking --
12587
12588
    dom.observe(focusBlurElement, interactionEvents, function() {
12589
      setTimeout(function() {
12590
        that.parent.fire("interaction").fire("interaction:composer");
12591
      }, 0);
12592
    });
12593
12594
12595
    if (this.config.handleTables) {
12596
      if(!this.tableClickHandle && this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
12597
        if (this.sandbox.getIframe) {
12598
          this.tableClickHandle = dom.observe(container , ["focus", "mouseup", "mouseover"], function() {
12599
            that.doc.execCommand("enableObjectResizing", false, "false");
12600
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12601
            that.tableClickHandle.stop();
12602
          });
12603
        } else {
12604
          setTimeout(function() {
12605
            that.doc.execCommand("enableObjectResizing", false, "false");
12606
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12607
          }, 0);
12608
        }
12609
      }
12610
      this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
12611
    }
12612
12613
    // --------- Focus & blur logic ---------
12614
    dom.observe(focusBlurElement, "focus", function(event) {
12615
      that.parent.fire("focus", event).fire("focus:composer", event);
12616
12617
      // Delay storing of state until all focus handler are fired
12618
      // especially the one which resets the placeholder
12619
      setTimeout(function() { state = that.getValue(false, false); }, 0);
12620
    });
12621
12622
    dom.observe(focusBlurElement, "blur", function(event) {
12623
      if (state !== that.getValue(false, false)) {
12624
        //create change event if supported (all except IE8)
12625
        var changeevent = event;
12626
        if(typeof Object.create == 'function') {
12627
          changeevent = Object.create(event, { type: { value: 'change' } });
12628
        }
12629
        that.parent.fire("change", changeevent).fire("change:composer", changeevent);
12630
      }
12631
      that.parent.fire("blur", event).fire("blur:composer", event);
12632
    });
12633
12634
    // --------- Drag & Drop logic ---------
12635
    dom.observe(element, "dragenter", function() {
12636
      that.parent.fire("unset_placeholder");
12637
    });
12638
12639
    dom.observe(element, pasteEvents, function(event) {
12640
      setTimeout(function() {
12641
        that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12642
      }, 0);
12643
    });
12644
12645
    // --------- neword event ---------
12646
    dom.observe(element, "keyup", function(event) {
12647
      var keyCode = event.keyCode;
12648
      if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
12649
        that.parent.fire("newword:composer");
12650
      }
12651
    });
12652
12653
    this.parent.on("paste:composer", function() {
12654
      setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
12655
    });
12656
12657
    // --------- Make sure that images are selected when clicking on them ---------
12658
    if (!browser.canSelectImagesInContentEditable()) {
12659
      dom.observe(element, "mousedown", function(event) {
12660
        var target = event.target;
12661
        var allImages = element.querySelectorAll('img'),
12662
            notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
12663
            myImages = wysihtml5.lang.array(allImages).without(notMyImages);
12664
12665
        if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
12666
          that.selection.selectNode(target);
12667
        }
12668
      });
12669
    }
12670
12671
    if (!browser.canSelectImagesInContentEditable()) {
12672
        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...
12673
            // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
12674
            setTimeout(function() {
12675
                that.selection.getSelection().removeAllRanges();
12676
            }, 0);
12677
        });
12678
    }
12679
12680
    if (browser.hasHistoryIssue() && browser.supportsSelectionModify()) {
12681
      dom.observe(element, "keydown", function(event) {
12682
        if (!event.metaKey && !event.ctrlKey) {
12683
          return;
12684
        }
12685
12686
        var keyCode   = event.keyCode,
12687
            win       = element.ownerDocument.defaultView,
12688
            selection = win.getSelection();
12689
12690
        if (keyCode === 37 || keyCode === 39) {
12691
          if (keyCode === 37) {
12692
            selection.modify("extend", "left", "lineboundary");
12693
            if (!event.shiftKey) {
12694
              selection.collapseToStart();
12695
            }
12696
          }
12697
          if (keyCode === 39) {
12698
            selection.modify("extend", "right", "lineboundary");
12699
            if (!event.shiftKey) {
12700
              selection.collapseToEnd();
12701
            }
12702
          }
12703
          event.preventDefault();
12704
        }
12705
      });
12706
    }
12707
12708
    // --------- Shortcut logic ---------
12709
    dom.observe(element, "keydown", function(event) {
12710
      var keyCode  = event.keyCode,
12711
          command  = shortcuts[keyCode];
12712
      if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
12713
        that.commands.exec(command);
12714
        event.preventDefault();
12715
      }
12716
      if (keyCode === 8) {
12717
        // delete key
12718
        handleDeleteKeyPress(event, that.selection, element, that);
12719
      } else if (that.config.handleTabKey && keyCode === 9) {
12720
        event.preventDefault();
12721
        handleTabKeyDown(that, element);
12722
      }
12723
    });
12724
12725
    // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
12726
    dom.observe(element, "keydown", function(event) {
12727
      var target  = that.selection.getSelectedNode(true),
12728
          keyCode = event.keyCode,
12729
          parent;
12730
      if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
12731
        parent = target.parentNode;
12732
        // delete the <img>
12733
        parent.removeChild(target);
12734
        // and it's parent <a> too if it hasn't got any other child nodes
12735
        if (parent.nodeName === "A" && !parent.firstChild) {
12736
          parent.parentNode.removeChild(parent);
12737
        }
12738
12739
        setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
12740
        event.preventDefault();
12741
      }
12742
    });
12743
12744
    // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
12745
    if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
12746
      dom.observe(container, "focus", function() {
12747
        setTimeout(function() {
12748
          if (that.doc.querySelector(":focus") !== that.element) {
12749
            that.focus();
12750
          }
12751
        }, 0);
12752
      });
12753
12754
      dom.observe(this.element, "blur", function() {
12755
        setTimeout(function() {
12756
          that.selection.getSelection().removeAllRanges();
12757
        }, 0);
12758
      });
12759
    }
12760
12761
    // --------- Show url in tooltip when hovering links or images ---------
12762
    var titlePrefixes = {
12763
      IMG: "Image: ",
12764
      A:   "Link: "
12765
    };
12766
12767
    dom.observe(element, "mouseover", function(event) {
12768
      var target   = event.target,
12769
          nodeName = target.nodeName,
12770
          title;
12771
      if (nodeName !== "A" && nodeName !== "IMG") {
12772
        return;
12773
      }
12774
      var hasTitle = target.hasAttribute("title");
12775
      if(!hasTitle){
12776
        title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
12777
        target.setAttribute("title", title);
12778
      }
12779
    });
12780
  };
12781
})(wysihtml5);
12782
;/**
12783
 * Class that takes care that the value of the composer and the textarea is always in sync
12784
 */
12785
(function(wysihtml5) {
12786
  var INTERVAL = 400;
12787
12788
  wysihtml5.views.Synchronizer = Base.extend(
12789
    /** @scope wysihtml5.views.Synchronizer.prototype */ {
12790
12791
    constructor: function(editor, textarea, composer) {
12792
      this.editor   = editor;
12793
      this.textarea = textarea;
12794
      this.composer = composer;
12795
12796
      this._observe();
12797
    },
12798
12799
    /**
12800
     * Sync html from composer to textarea
12801
     * Takes care of placeholders
12802
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
12803
     */
12804
    fromComposerToTextarea: function(shouldParseHtml) {
12805
      this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
12806
    },
12807
12808
    /**
12809
     * Sync value of textarea to composer
12810
     * Takes care of placeholders
12811
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
12812
     */
12813
    fromTextareaToComposer: function(shouldParseHtml) {
12814
      var textareaValue = this.textarea.getValue(false, false);
12815
      if (textareaValue) {
12816
        this.composer.setValue(textareaValue, shouldParseHtml);
12817
      } else {
12818
        this.composer.clear();
12819
        this.editor.fire("set_placeholder");
12820
      }
12821
    },
12822
12823
    /**
12824
     * Invoke syncing based on view state
12825
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
12826
     */
12827
    sync: function(shouldParseHtml) {
12828
      if (this.editor.currentView.name === "textarea") {
12829
        this.fromTextareaToComposer(shouldParseHtml);
12830
      } else {
12831
        this.fromComposerToTextarea(shouldParseHtml);
12832
      }
12833
    },
12834
12835
    /**
12836
     * Initializes interval-based syncing
12837
     * also makes sure that on-submit the composer's content is synced with the textarea
12838
     * immediately when the form gets submitted
12839
     */
12840
    _observe: function() {
12841
      var interval,
12842
          that          = this,
12843
          form          = this.textarea.element.form,
12844
          startInterval = function() {
12845
            interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
12846
          },
12847
          stopInterval  = function() {
12848
            clearInterval(interval);
12849
            interval = null;
12850
          };
12851
12852
      startInterval();
12853
12854
      if (form) {
12855
        // If the textarea is in a form make sure that after onreset and onsubmit the composer
12856
        // has the correct state
12857
        wysihtml5.dom.observe(form, "submit", function() {
12858
          that.sync(true);
12859
        });
12860
        wysihtml5.dom.observe(form, "reset", function() {
12861
          setTimeout(function() { that.fromTextareaToComposer(); }, 0);
12862
        });
12863
      }
12864
12865
      this.editor.on("change_view", function(view) {
12866
        if (view === "composer" && !interval) {
12867
          that.fromTextareaToComposer(true);
12868
          startInterval();
12869
        } else if (view === "textarea") {
12870
          that.fromComposerToTextarea(true);
12871
          stopInterval();
12872
        }
12873
      });
12874
12875
      this.editor.on("destroy:composer", stopInterval);
12876
    }
12877
  });
12878
})(wysihtml5);
12879
;wysihtml5.views.Textarea = wysihtml5.views.View.extend(
12880
  /** @scope wysihtml5.views.Textarea.prototype */ {
12881
  name: "textarea",
12882
12883
  constructor: function(parent, textareaElement, config) {
12884
    this.base(parent, textareaElement, config);
12885
12886
    this._observe();
12887
  },
12888
12889
  clear: function() {
12890
    this.element.value = "";
12891
  },
12892
12893
  getValue: function(parse) {
12894
    var value = this.isEmpty() ? "" : this.element.value;
12895
    if (parse !== false) {
12896
      value = this.parent.parse(value);
12897
    }
12898
    return value;
12899
  },
12900
12901
  setValue: function(html, parse) {
12902
    if (parse) {
12903
      html = this.parent.parse(html);
12904
    }
12905
    this.element.value = html;
12906
  },
12907
12908
  cleanUp: function() {
12909
      var html = this.parent.parse(this.element.value);
12910
      this.element.value = html;
12911
  },
12912
12913
  hasPlaceholderSet: function() {
12914
    var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),
12915
        placeholderText     = this.element.getAttribute("placeholder") || null,
12916
        value               = this.element.value,
12917
        isEmpty             = !value;
12918
    return (supportsPlaceholder && isEmpty) || (value === placeholderText);
12919
  },
12920
12921
  isEmpty: function() {
12922
    return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
12923
  },
12924
12925
  _observe: function() {
12926
    var element = this.element,
12927
        parent  = this.parent,
12928
        eventMapping = {
12929
          focusin:  "focus",
12930
          focusout: "blur"
12931
        },
12932
        /**
12933
         * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
12934
         * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
12935
         */
12936
        events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
12937
12938
    parent.on("beforeload", function() {
12939
      wysihtml5.dom.observe(element, events, function(event) {
12940
        var eventName = eventMapping[event.type] || event.type;
12941
        parent.fire(eventName).fire(eventName + ":textarea");
12942
      });
12943
12944
      wysihtml5.dom.observe(element, ["paste", "drop"], function() {
12945
        setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
12946
      });
12947
    });
12948
  }
12949
});
12950
;/**
12951
 * WYSIHTML5 Editor
12952
 *
12953
 * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
12954
 * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
12955
 *
12956
 * @events
12957
 *    load
12958
 *    beforeload (for internal use only)
12959
 *    focus
12960
 *    focus:composer
12961
 *    focus:textarea
12962
 *    blur
12963
 *    blur:composer
12964
 *    blur:textarea
12965
 *    change
12966
 *    change:composer
12967
 *    change:textarea
12968
 *    paste
12969
 *    paste:composer
12970
 *    paste:textarea
12971
 *    newword:composer
12972
 *    destroy:composer
12973
 *    undo:composer
12974
 *    redo:composer
12975
 *    beforecommand:composer
12976
 *    aftercommand:composer
12977
 *    enable:composer
12978
 *    disable:composer
12979
 *    change_view
12980
 */
12981
(function(wysihtml5) {
12982
  var undef;
12983
12984
  var defaultConfig = {
12985
    // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
12986
    name:                 undef,
0 ignored issues
show
Bug introduced by
The variable undef seems to be never initialized.
Loading history...
12987
    // Whether the editor should look like the textarea (by adopting styles)
12988
    style:                true,
12989
    // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
12990
    toolbar:              undef,
12991
    // Whether toolbar is displayed after init by script automatically.
12992
    // Can be set to false if toolobar is set to display only on editable area focus
12993
    showToolbarAfterInit: true,
12994
    // Whether urls, entered by the user should automatically become clickable-links
12995
    autoLink:             true,
12996
    // Includes table editing events and cell selection tracking
12997
    handleTables:         true,
12998
    // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
12999
    handleTabKey:         true,
13000
    // Object which includes parser rules to apply when html gets inserted via copy & paste
13001
    // See parser_rules/*.js for examples
13002
    parserRules:          { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
13003
    // Parser method to use when the user inserts content via copy & paste
13004
    parser:               wysihtml5.dom.parse,
13005
    // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
13006
    composerClassName:    "wysihtml5-editor",
13007
    // Class name to add to the body when the wysihtml5 editor is supported
13008
    bodyClassName:        "wysihtml5-supported",
13009
    // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
13010
    useLineBreaks:        true,
13011
    // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
13012
    stylesheets:          [],
13013
    // Placeholder text to use, defaults to the placeholder attribute on the textarea element
13014
    placeholderText:      undef,
13015
    // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
13016
    supportTouchDevices:  true,
13017
    // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
13018
    cleanUp:              true,
13019
    // Whether to use div instead of secure iframe
13020
    contentEditableMode: false,
13021
    // Classname of container that editor should not touch and pass through
13022
    // Pass false to disable
13023
    uneditableContainerClassname: "wysihtml5-uneditable-container"
13024
  };
13025
13026
  wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
13027
    /** @scope wysihtml5.Editor.prototype */ {
13028
    constructor: function(editableElement, config) {
13029
      this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
13030
      this.config           = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
13031
      this._isCompatible    = wysihtml5.browser.supported();
13032
13033
      if (this.editableElement.nodeName.toLowerCase() != "textarea") {
13034
          this.config.contentEditableMode = true;
13035
          this.config.noTextarea = true;
13036
      }
13037
      if (!this.config.noTextarea) {
13038
          this.textarea         = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
13039
          this.currentView      = this.textarea;
13040
      }
13041
13042
      // Sort out unsupported/unwanted browsers here
13043
      if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
13044
        var that = this;
13045
        setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
13046
        return;
13047
      }
13048
13049
      // Add class name to body, to indicate that the editor is supported
13050
      wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
13051
13052
      this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
13053
      this.currentView = this.composer;
13054
13055
      if (typeof(this.config.parser) === "function") {
13056
        this._initParser();
13057
      }
13058
13059
      this.on("beforeload", this.handleBeforeLoad);
13060
    },
13061
13062
    handleBeforeLoad: function() {
13063
        if (!this.config.noTextarea) {
13064
            this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
13065
        }
13066
        if (this.config.toolbar) {
13067
          this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);
13068
        }
13069
    },
13070
13071
    isCompatible: function() {
13072
      return this._isCompatible;
13073
    },
13074
13075
    clear: function() {
13076
      this.currentView.clear();
13077
      return this;
13078
    },
13079
13080
    getValue: function(parse, clearInternals) {
13081
      return this.currentView.getValue(parse, clearInternals);
13082
    },
13083
13084
    setValue: function(html, parse) {
13085
      this.fire("unset_placeholder");
13086
13087
      if (!html) {
13088
        return this.clear();
13089
      }
13090
13091
      this.currentView.setValue(html, parse);
13092
      return this;
13093
    },
13094
13095
    cleanUp: function() {
13096
        this.currentView.cleanUp();
13097
    },
13098
13099
    focus: function(setToEnd) {
13100
      this.currentView.focus(setToEnd);
13101
      return this;
13102
    },
13103
13104
    /**
13105
     * Deactivate editor (make it readonly)
13106
     */
13107
    disable: function() {
13108
      this.currentView.disable();
13109
      return this;
13110
    },
13111
13112
    /**
13113
     * Activate editor
13114
     */
13115
    enable: function() {
13116
      this.currentView.enable();
13117
      return this;
13118
    },
13119
13120
    isEmpty: function() {
13121
      return this.currentView.isEmpty();
13122
    },
13123
13124
    hasPlaceholderSet: function() {
13125
      return this.currentView.hasPlaceholderSet();
13126
    },
13127
13128
    parse: function(htmlOrElement, clearInternals) {
13129
      var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
13130
      var returnValue = this.config.parser(htmlOrElement, {
13131
        "rules": this.config.parserRules,
13132
        "cleanUp": this.config.cleanUp,
13133
        "context": parseContext,
13134
        "uneditableClass": this.config.uneditableContainerClassname,
13135
        "clearInternals" : clearInternals
13136
      });
13137
      if (typeof(htmlOrElement) === "object") {
13138
        wysihtml5.quirks.redraw(htmlOrElement);
13139
      }
13140
      return returnValue;
13141
    },
13142
13143
    /**
13144
     * Prepare html parser logic
13145
     *  - Observes for paste and drop
13146
     */
13147
    _initParser: function() {
13148
      this.on("paste:composer", function() {
13149
        var keepScrollPosition  = true,
13150
            that                = this;
13151
        that.composer.selection.executeAndRestore(function() {
13152
          wysihtml5.quirks.cleanPastedHTML(that.composer.element);
13153
          that.parse(that.composer.element);
13154
        }, keepScrollPosition);
13155
      });
13156
    }
13157
  });
13158
})(wysihtml5);
13159
;/**
13160
 * Toolbar Dialog
13161
 *
13162
 * @param {Element} link The toolbar link which causes the dialog to show up
13163
 * @param {Element} container The dialog container
13164
 *
13165
 * @example
13166
 *    <!-- Toolbar link -->
13167
 *    <a data-wysihtml5-command="insertImage">insert an image</a>
13168
 *
13169
 *    <!-- Dialog -->
13170
 *    <div data-wysihtml5-dialog="insertImage" style="display: none;">
13171
 *      <label>
13172
 *        URL: <input data-wysihtml5-dialog-field="src" value="http://">
13173
 *      </label>
13174
 *      <label>
13175
 *        Alternative text: <input data-wysihtml5-dialog-field="alt" value="">
13176
 *      </label>
13177
 *    </div>
13178
 *
13179
 *    <script>
13180
 *      var dialog = new wysihtml5.toolbar.Dialog(
13181
 *        document.querySelector("[data-wysihtml5-command='insertImage']"),
13182
 *        document.querySelector("[data-wysihtml5-dialog='insertImage']")
13183
 *      );
13184
 *      dialog.observe("save", function(attributes) {
13185
 *        // do something
13186
 *      });
13187
 *    </script>
13188
 */
13189
(function(wysihtml5) {
13190
  var dom                     = wysihtml5.dom,
13191
      CLASS_NAME_OPENED       = "wysihtml5-command-dialog-opened",
13192
      SELECTOR_FORM_ELEMENTS  = "input, select, textarea",
13193
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
13194
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
13195
13196
13197
  wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend(
13198
    /** @scope wysihtml5.toolbar.Dialog.prototype */ {
13199
    constructor: function(link, container) {
13200
      this.link       = link;
13201
      this.container  = container;
13202
    },
13203
13204
    _observe: function() {
13205
      if (this._observed) {
13206
        return;
13207
      }
13208
13209
      var that = this,
13210
          callbackWrapper = function(event) {
13211
            var attributes = that._serialize();
13212
            if (attributes == that.elementToChange) {
13213
              that.fire("edit", attributes);
13214
            } else {
13215
              that.fire("save", attributes);
13216
            }
13217
            that.hide();
13218
            event.preventDefault();
13219
            event.stopPropagation();
13220
          };
13221
13222
      dom.observe(that.link, "click", function() {
13223
        if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
13224
          setTimeout(function() { that.hide(); }, 0);
13225
        }
13226
      });
13227
13228
      dom.observe(this.container, "keydown", function(event) {
13229
        var keyCode = event.keyCode;
13230
        if (keyCode === wysihtml5.ENTER_KEY) {
13231
          callbackWrapper(event);
13232
        }
13233
        if (keyCode === wysihtml5.ESCAPE_KEY) {
13234
          that.fire("cancel");
13235
          that.hide();
13236
        }
13237
      });
13238
13239
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
13240
13241
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
13242
        that.fire("cancel");
13243
        that.hide();
13244
        event.preventDefault();
13245
        event.stopPropagation();
13246
      });
13247
13248
      var formElements  = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
13249
          i             = 0,
13250
          length        = formElements.length,
13251
          _clearInterval = function() { clearInterval(that.interval); };
13252
      for (; i<length; i++) {
13253
        dom.observe(formElements[i], "change", _clearInterval);
13254
      }
13255
13256
      this._observed = true;
13257
    },
13258
13259
    /**
13260
     * Grabs all fields in the dialog and puts them in key=>value style in an object which
13261
     * then gets returned
13262
     */
13263
    _serialize: function() {
13264
      var data    = this.elementToChange || {},
13265
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
13266
          length  = fields.length,
13267
          i       = 0;
13268
13269
      for (; i<length; i++) {
13270
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
13271
      }
13272
      return data;
13273
    },
13274
13275
    /**
13276
     * Takes the attributes of the "elementToChange"
13277
     * and inserts them in their corresponding dialog input fields
13278
     *
13279
     * Assume the "elementToChange" looks like this:
13280
     *    <a href="http://www.google.com" target="_blank">foo</a>
13281
     *
13282
     * and we have the following dialog:
13283
     *    <input type="text" data-wysihtml5-dialog-field="href" value="">
13284
     *    <input type="text" data-wysihtml5-dialog-field="target" value="">
13285
     *
13286
     * after calling _interpolate() the dialog will look like this
13287
     *    <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com">
13288
     *    <input type="text" data-wysihtml5-dialog-field="target" value="_blank">
13289
     *
13290
     * Basically it adopted the attribute values into the corresponding input fields
13291
     *
13292
     */
13293
    _interpolate: function(avoidHiddenFields) {
13294
      var field,
13295
          fieldName,
13296
          newValue,
13297
          focusedElement = document.querySelector(":focus"),
13298
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
13299
          length         = fields.length,
13300
          i              = 0;
13301
      for (; i<length; i++) {
13302
        field = fields[i];
13303
13304
        // Never change elements where the user is currently typing in
13305
        if (field === focusedElement) {
13306
          continue;
13307
        }
13308
13309
        // Don't update hidden fields
13310
        // See https://github.com/xing/wysihtml5/pull/14
13311
        if (avoidHiddenFields && field.type === "hidden") {
13312
          continue;
13313
        }
13314
13315
        fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
13316
        newValue  = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue;
13317
        field.value = newValue;
13318
      }
13319
    },
13320
13321
    /**
13322
     * Show the dialog element
13323
     */
13324
    show: function(elementToChange) {
13325
      if (dom.hasClass(this.link, CLASS_NAME_OPENED)) {
13326
        return;
13327
      }
13328
13329
      var that        = this,
13330
          firstField  = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
13331
      this.elementToChange = elementToChange;
13332
      this._observe();
13333
      this._interpolate();
13334
      if (elementToChange) {
13335
        this.interval = setInterval(function() { that._interpolate(true); }, 500);
13336
      }
13337
      dom.addClass(this.link, CLASS_NAME_OPENED);
13338
      this.container.style.display = "";
13339
      this.fire("show");
13340
      if (firstField && !elementToChange) {
13341
        try {
13342
          firstField.focus();
13343
        } 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...
13344
      }
13345
    },
13346
13347
    /**
13348
     * Hide the dialog element
13349
     */
13350
    hide: function() {
13351
      clearInterval(this.interval);
13352
      this.elementToChange = null;
13353
      dom.removeClass(this.link, CLASS_NAME_OPENED);
13354
      this.container.style.display = "none";
13355
      this.fire("hide");
13356
    }
13357
  });
13358
})(wysihtml5);
13359
;/**
13360
 * Converts speech-to-text and inserts this into the editor
13361
 * As of now (2011/03/25) this only is supported in Chrome >= 11
13362
 *
13363
 * Note that it sends the recorded audio to the google speech recognition api:
13364
 * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
13365
 *
13366
 * Current HTML5 draft can be found here
13367
 * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
13368
 *
13369
 * "Accessing Google Speech API Chrome 11"
13370
 * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
13371
 */
13372
(function(wysihtml5) {
13373
  var dom = wysihtml5.dom;
13374
13375
  var linkStyles = {
13376
    position: "relative"
13377
  };
13378
13379
  var wrapperStyles = {
13380
    left:     0,
13381
    margin:   0,
13382
    opacity:  0,
13383
    overflow: "hidden",
13384
    padding:  0,
13385
    position: "absolute",
13386
    top:      0,
13387
    zIndex:   1
13388
  };
13389
13390
  var inputStyles = {
13391
    cursor:     "inherit",
13392
    fontSize:   "50px",
13393
    height:     "50px",
13394
    marginTop:  "-25px",
13395
    outline:    0,
13396
    padding:    0,
13397
    position:   "absolute",
13398
    right:      "-4px",
13399
    top:        "50%"
13400
  };
13401
13402
  var inputAttributes = {
13403
    "x-webkit-speech": "",
13404
    "speech":          ""
13405
  };
13406
13407
  wysihtml5.toolbar.Speech = function(parent, link) {
13408
    var input = document.createElement("input");
13409
    if (!wysihtml5.browser.supportsSpeechApiOn(input)) {
13410
      link.style.display = "none";
13411
      return;
13412
    }
13413
    var lang = parent.editor.textarea.element.getAttribute("lang");
13414
    if (lang) {
13415
      inputAttributes.lang = lang;
13416
    }
13417
13418
    var wrapper = document.createElement("div");
13419
13420
    wysihtml5.lang.object(wrapperStyles).merge({
13421
      width:  link.offsetWidth  + "px",
13422
      height: link.offsetHeight + "px"
13423
    });
13424
13425
    dom.insert(input).into(wrapper);
13426
    dom.insert(wrapper).into(link);
13427
13428
    dom.setStyles(inputStyles).on(input);
13429
    dom.setAttributes(inputAttributes).on(input);
13430
13431
    dom.setStyles(wrapperStyles).on(wrapper);
13432
    dom.setStyles(linkStyles).on(link);
13433
13434
    var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
13435
    dom.observe(input, eventName, function() {
13436
      parent.execCommand("insertText", input.value);
13437
      input.value = "";
13438
    });
13439
13440
    dom.observe(input, "click", function(event) {
13441
      if (dom.hasClass(link, "wysihtml5-command-disabled")) {
13442
        event.preventDefault();
13443
      }
13444
13445
      event.stopPropagation();
13446
    });
13447
  };
13448
})(wysihtml5);
13449
;/**
13450
 * Toolbar
13451
 *
13452
 * @param {Object} parent Reference to instance of Editor instance
13453
 * @param {Element} container Reference to the toolbar container element
13454
 *
13455
 * @example
13456
 *    <div id="toolbar">
13457
 *      <a data-wysihtml5-command="createLink">insert link</a>
13458
 *      <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
13459
 *    </div>
13460
 *
13461
 *    <script>
13462
 *      var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
13463
 *    </script>
13464
 */
13465
(function(wysihtml5) {
13466
  var CLASS_NAME_COMMAND_DISABLED   = "wysihtml5-command-disabled",
13467
      CLASS_NAME_COMMANDS_DISABLED  = "wysihtml5-commands-disabled",
13468
      CLASS_NAME_COMMAND_ACTIVE     = "wysihtml5-command-active",
13469
      CLASS_NAME_ACTION_ACTIVE      = "wysihtml5-action-active",
13470
      dom                           = wysihtml5.dom;
13471
13472
  wysihtml5.toolbar.Toolbar = Base.extend(
13473
    /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
13474
    constructor: function(editor, container, showOnInit) {
13475
      this.editor     = editor;
13476
      this.container  = typeof(container) === "string" ? document.getElementById(container) : container;
13477
      this.composer   = editor.composer;
13478
13479
      this._getLinks("command");
13480
      this._getLinks("action");
13481
13482
      this._observe();
13483
      if (showOnInit) { this.show(); }
13484
13485
      var speechInputLinks  = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
13486
          length            = speechInputLinks.length,
13487
          i                 = 0;
13488
      for (; i<length; i++) {
13489
        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...
13490
      }
13491
    },
13492
13493
    _getLinks: function(type) {
13494
      var links   = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
13495
          length  = links.length,
13496
          i       = 0,
13497
          mapping = this[type + "Mapping"] = {},
13498
          link,
13499
          group,
13500
          name,
13501
          value,
13502
          dialog;
13503
      for (; i<length; i++) {
13504
        link    = links[i];
13505
        name    = link.getAttribute("data-wysihtml5-" + type);
13506
        value   = link.getAttribute("data-wysihtml5-" + type + "-value");
13507
        group   = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
13508
        dialog  = this._getDialog(link, name);
13509
13510
        mapping[name + ":" + value] = {
13511
          link:   link,
13512
          group:  group,
13513
          name:   name,
13514
          value:  value,
13515
          dialog: dialog,
13516
          state:  false
13517
        };
13518
      }
13519
    },
13520
13521
    _getDialog: function(link, command) {
13522
      var that          = this,
13523
          dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
13524
          dialog,
13525
          caretBookmark;
13526
13527
      if (dialogElement) {
13528
        if (wysihtml5.toolbar["Dialog_" + command]) {
13529
            dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
13530
        } else {
13531
            dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
13532
        }
13533
13534
        dialog.on("show", function() {
13535
          caretBookmark = that.composer.selection.getBookmark();
13536
13537
          that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13538
        });
13539
13540
        dialog.on("save", function(attributes) {
13541
          if (caretBookmark) {
13542
            that.composer.selection.setBookmark(caretBookmark);
13543
          }
13544
          that._execCommand(command, attributes);
13545
13546
          that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13547
        });
13548
13549
        dialog.on("cancel", function() {
13550
          that.editor.focus(false);
13551
          that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13552
        });
13553
      }
13554
      return dialog;
0 ignored issues
show
Bug introduced by
The variable dialog does not seem to be initialized in case dialogElement on line 13527 is false. Are you sure this can never be the case?
Loading history...
13555
    },
13556
13557
    /**
13558
     * @example
13559
     *    var toolbar = new wysihtml5.Toolbar();
13560
     *    // Insert a <blockquote> element or wrap current selection in <blockquote>
13561
     *    toolbar.execCommand("formatBlock", "blockquote");
13562
     */
13563
    execCommand: function(command, commandValue) {
13564
      if (this.commandsDisabled) {
13565
        return;
13566
      }
13567
13568
      var commandObj = this.commandMapping[command + ":" + commandValue];
13569
13570
      // Show dialog when available
13571
      if (commandObj && commandObj.dialog && !commandObj.state) {
13572
        commandObj.dialog.show();
13573
      } else {
13574
        this._execCommand(command, commandValue);
13575
      }
13576
    },
13577
13578
    _execCommand: function(command, commandValue) {
13579
      // Make sure that composer is focussed (false => don't move caret to the end)
13580
      this.editor.focus(false);
13581
13582
      this.composer.commands.exec(command, commandValue);
13583
      this._updateLinkStates();
13584
    },
13585
13586
    execAction: function(action) {
13587
      var editor = this.editor;
13588
      if (action === "change_view") {
13589
        if (editor.textarea) {
13590
            if (editor.currentView === editor.textarea) {
13591
              editor.fire("change_view", "composer");
13592
            } else {
13593
              editor.fire("change_view", "textarea");
13594
            }
13595
        }
13596
      }
13597
      if (action == "showSource") {
13598
          editor.fire("showSource");
13599
      }
13600
    },
13601
13602
    _observe: function() {
13603
      var that      = this,
13604
          editor    = this.editor,
13605
          container = this.container,
13606
          links     = this.commandLinks.concat(this.actionLinks),
13607
          length    = links.length,
13608
          i         = 0;
13609
13610
      for (; i<length; i++) {
13611
        // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
13612
        // (you know, a:link { ... } doesn't match anchors with missing href attribute)
13613
        if (links[i].nodeName === "A") {
13614
          dom.setAttributes({
13615
            href:         "javascript:;",
13616
            unselectable: "on"
13617
          }).on(links[i]);
13618
        } else {
13619
          dom.setAttributes({ unselectable: "on" }).on(links[i]);
13620
        }
13621
      }
13622
13623
      // Needed for opera and chrome
13624
      dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
13625
13626
      dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
13627
        var link          = this,
13628
            command       = link.getAttribute("data-wysihtml5-command"),
13629
            commandValue  = link.getAttribute("data-wysihtml5-command-value");
13630
        that.execCommand(command, commandValue);
13631
        event.preventDefault();
13632
      });
13633
13634
      dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
13635
        var action = this.getAttribute("data-wysihtml5-action");
13636
        that.execAction(action);
13637
        event.preventDefault();
13638
      });
13639
13640
      editor.on("interaction:composer", function() {
13641
          that._updateLinkStates();
13642
      });
13643
13644
      editor.on("focus:composer", function() {
13645
        that.bookmark = null;
13646
      });
13647
13648
      if (this.editor.config.handleTables) {
13649
          editor.on("tableselect:composer", function() {
13650
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
13651
          });
13652
          editor.on("tableunselect:composer", function() {
13653
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
13654
          });
13655
      }
13656
13657
      editor.on("change_view", function(currentView) {
13658
        // Set timeout needed in order to let the blur event fire first
13659
        if (editor.textarea) {
13660
            setTimeout(function() {
13661
              that.commandsDisabled = (currentView !== "composer");
13662
              that._updateLinkStates();
13663
              if (that.commandsDisabled) {
13664
                dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
13665
              } else {
13666
                dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
13667
              }
13668
            }, 0);
13669
        }
13670
      });
13671
    },
13672
13673
    _updateLinkStates: function() {
13674
13675
      var commandMapping    = this.commandMapping,
13676
          actionMapping     = this.actionMapping,
13677
          i,
13678
          state,
13679
          action,
13680
          command;
13681
      // every millisecond counts... this is executed quite often
13682
      for (i in commandMapping) {
13683
        command = commandMapping[i];
13684
        if (this.commandsDisabled) {
13685
          state = false;
13686
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13687
          if (command.group) {
13688
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13689
          }
13690
          if (command.dialog) {
13691
            command.dialog.hide();
13692
          }
13693
        } else {
13694
          state = this.composer.commands.state(command.name, command.value);
13695
          dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
13696
          if (command.group) {
13697
            dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
13698
          }
13699
        }
13700
        if (command.state === state) {
13701
          continue;
13702
        }
13703
13704
        command.state = state;
13705
        if (state) {
13706
          dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13707
          if (command.group) {
13708
            dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13709
          }
13710
          if (command.dialog) {
13711
            if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
13712
13713
              if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
13714
                // Grab first and only object/element in state array, otherwise convert state into boolean
13715
                // to avoid showing a dialog for multiple selected elements which may have different attributes
13716
                // eg. when two links with different href are selected, the state will be an array consisting of both link elements
13717
                // but the dialog interface can only update one
13718
                state = state.length === 1 ? state[0] : true;
13719
                command.state = state;
13720
              }
13721
              command.dialog.show(state);
13722
            } else {
13723
              command.dialog.hide();
13724
            }
13725
          }
13726
        } else {
13727
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13728
          if (command.group) {
13729
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13730
          }
13731
          if (command.dialog) {
13732
            command.dialog.hide();
13733
          }
13734
        }
13735
      }
13736
13737
      for (i in actionMapping) {
13738
        action = actionMapping[i];
13739
13740
        if (action.name === "change_view") {
13741
          action.state = this.editor.currentView === this.editor.textarea;
13742
          if (action.state) {
13743
            dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
13744
          } else {
13745
            dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
13746
          }
13747
        }
13748
      }
13749
    },
13750
13751
    show: function() {
13752
      this.container.style.display = "";
13753
    },
13754
13755
    hide: function() {
13756
      this.container.style.display = "none";
13757
    }
13758
  });
13759
13760
})(wysihtml5);
13761
;(function(wysihtml5) {
13762
    wysihtml5.toolbar.Dialog_createTable = wysihtml5.toolbar.Dialog.extend({
13763
        show: function(elementToChange) {
13764
            this.base(elementToChange);
13765
13766
        }
13767
13768
    });
13769
})(wysihtml5);
13770
;(function(wysihtml5) {
13771
  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...
13772
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
13773
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
13774
13775
  wysihtml5.toolbar.Dialog_foreColorStyle = wysihtml5.toolbar.Dialog.extend({
13776
    multiselect: true,
13777
13778
    _serialize: function() {
13779
      var data    = {},
13780
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
13781
          length  = fields.length,
13782
          i       = 0;
13783
13784
      for (; i<length; i++) {
13785
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
13786
      }
13787
      return data;
13788
    },
13789
13790
    _interpolate: function(avoidHiddenFields) {
13791
      var field,
13792
          fieldName,
0 ignored issues
show
Unused Code introduced by
The variable fieldName seems to be never used. Consider removing it.
Loading history...
13793
          newValue,
0 ignored issues
show
Unused Code introduced by
The variable newValue seems to be never used. Consider removing it.
Loading history...
13794
          focusedElement = document.querySelector(":focus"),
13795
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
13796
          length         = fields.length,
13797
          i              = 0,
13798
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
13799
          colorStr       = (firstElement) ? firstElement.getAttribute('style') : null,
13800
          color          = (colorStr) ? wysihtml5.quirks.styleParser.parseColor(colorStr, "color") : null;
13801
13802
      for (; i<length; i++) {
13803
        field = fields[i];
13804
        // Never change elements where the user is currently typing in
13805
        if (field === focusedElement) {
13806
          continue;
13807
        }
13808
        // Don't update hidden fields3
13809
        if (avoidHiddenFields && field.type === "hidden") {
13810
          continue;
13811
        }
13812
        if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
13813
          if (color) {
13814
            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...
13815
              field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
13816
            } else {
13817
              field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
13818
            }
13819
          } else {
13820
            field.value = "rgb(0,0,0);";
13821
          }
13822
        }
13823
      }
13824
    }
13825
13826
  });
13827
})(wysihtml5);
13828
;(function(wysihtml5) {
13829
  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...
13830
      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...
13831
      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...
13832
13833
  wysihtml5.toolbar.Dialog_fontSizeStyle = wysihtml5.toolbar.Dialog.extend({
13834
    multiselect: true,
13835
13836
    _serialize: function() {
13837
      return {"size" : this.container.querySelector('[data-wysihtml5-dialog-field="size"]').value};
13838
    },
13839
13840
    _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...
13841
      var focusedElement = document.querySelector(":focus"),
13842
          field          = this.container.querySelector("[data-wysihtml5-dialog-field='size']"),
13843
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
13844
          styleStr       = (firstElement) ? firstElement.getAttribute('style') : null,
13845
          size           = (styleStr) ? wysihtml5.quirks.styleParser.parseFontSize(styleStr) : null;
13846
13847
      if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) {
13848
        field.value = size;
13849
      }
13850
    }
13851
13852
  });
13853
})(wysihtml5);
13854
13855
13856
return wysihtml5;
13857
});/*!
13858
13859
 handlebars v1.3.0
13860
13861
Copyright (C) 2011 by Yehuda Katz
13862
13863
Permission is hereby granted, free of charge, to any person obtaining a copy
13864
of this software and associated documentation files (the "Software"), to deal
13865
in the Software without restriction, including without limitation the rights
13866
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13867
copies of the Software, and to permit persons to whom the Software is
13868
furnished to do so, subject to the following conditions:
13869
13870
The above copyright notice and this permission notice shall be included in
13871
all copies or substantial portions of the Software.
13872
13873
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13874
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13875
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13876
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
13877
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
13878
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
13879
THE SOFTWARE.
13880
13881
@license
13882
*/
13883
13884
define(
13885
  'handlebars/safe-string',["exports"],
13886
  function(__exports__) {
13887
    
13888
    // Build out our basic SafeString type
13889
    function SafeString(string) {
13890
      this.string = string;
13891
    }
13892
13893
    SafeString.prototype.toString = function() {
13894
      return "" + this.string;
13895
    };
13896
13897
    __exports__["default"] = SafeString;
13898
  });
13899
define(
13900
  'handlebars/utils',["./safe-string","exports"],
13901 View Code Duplication
  function(__dependency1__, __exports__) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
13902
    
13903
    /*jshint -W004 */
13904
    var SafeString = __dependency1__["default"];
13905
13906
    var escape = {
13907
      "&": "&amp;",
13908
      "<": "&lt;",
13909
      ">": "&gt;",
13910
      '"': "&quot;",
13911
      "'": "&#x27;",
13912
      "`": "&#x60;"
13913
    };
13914
13915
    var badChars = /[&<>"'`]/g;
13916
    var possible = /[&<>"'`]/;
13917
13918
    function escapeChar(chr) {
13919
      return escape[chr] || "&amp;";
13920
    }
13921
13922
    function extend(obj, value) {
13923
      for(var key 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...
13924
        if(Object.prototype.hasOwnProperty.call(value, key)) {
13925
          obj[key] = value[key];
13926
        }
13927
      }
13928
    }
13929
13930
    __exports__.extend = extend;var toString = Object.prototype.toString;
13931
    __exports__.toString = toString;
13932
    // Sourced from lodash
13933
    // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
13934
    var isFunction = function(value) {
13935
      return typeof value === 'function';
13936
    };
13937
    // fallback for older versions of Chrome and Safari
13938
    if (isFunction(/x/)) {
13939
      isFunction = function(value) {
13940
        return typeof value === 'function' && toString.call(value) === '[object Function]';
13941
      };
13942
    }
13943
    var isFunction;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable isFunction already seems to be declared on line 13934. 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...
13944
    __exports__.isFunction = isFunction;
13945
    var isArray = Array.isArray || function(value) {
13946
      return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
13947
    };
13948
    __exports__.isArray = isArray;
13949
13950
    function escapeExpression(string) {
13951
      // don't escape SafeStrings, since they're already safe
13952
      if (string instanceof SafeString) {
13953
        return string.toString();
13954
      } else if (!string && string !== 0) {
13955
        return "";
13956
      }
13957
13958
      // Force a string conversion as this will be done by the append regardless and
13959
      // the regex test will do this transparently behind the scenes, causing issues if
13960
      // an object's to string has escaped characters in it.
13961
      string = "" + string;
13962
13963
      if(!possible.test(string)) { return string; }
13964
      return string.replace(badChars, escapeChar);
13965
    }
13966
13967
    __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
13968
      if (!value && value !== 0) {
13969
        return true;
13970
      } else if (isArray(value) && value.length === 0) {
13971
        return true;
13972
      } else {
13973
        return false;
13974
      }
13975
    }
13976
13977
    __exports__.isEmpty = isEmpty;
13978
  });
13979
define(
13980
  'handlebars/exception',["exports"],
13981 View Code Duplication
  function(__exports__) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
13982
    
13983
13984
    var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
13985
13986
    function Exception(message, node) {
13987
      var line;
13988
      if (node && node.firstLine) {
13989
        line = node.firstLine;
13990
13991
        message += ' - ' + line + ':' + node.firstColumn;
13992
      }
13993
13994
      var tmp = Error.prototype.constructor.call(this, message);
13995
13996
      // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
13997
      for (var idx = 0; idx < errorProps.length; idx++) {
13998
        this[errorProps[idx]] = tmp[errorProps[idx]];
13999
      }
14000
14001
      if (line) {
14002
        this.lineNumber = line;
14003
        this.column = node.firstColumn;
14004
      }
14005
    }
14006
14007
    Exception.prototype = new Error();
14008
14009
    __exports__["default"] = Exception;
14010
  });
14011
define(
14012
  'handlebars/base',["./utils","./exception","exports"],
14013 View Code Duplication
  function(__dependency1__, __dependency2__, __exports__) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14014
    
14015
    var Utils = __dependency1__;
14016
    var Exception = __dependency2__["default"];
14017
14018
    var VERSION = "1.3.0";
14019
    __exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
14020
    __exports__.COMPILER_REVISION = COMPILER_REVISION;
14021
    var REVISION_CHANGES = {
14022
      1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
14023
      2: '== 1.0.0-rc.3',
14024
      3: '== 1.0.0-rc.4',
14025
      4: '>= 1.0.0'
14026
    };
14027
    __exports__.REVISION_CHANGES = REVISION_CHANGES;
14028
    var isArray = Utils.isArray,
14029
        isFunction = Utils.isFunction,
14030
        toString = Utils.toString,
14031
        objectType = '[object Object]';
14032
14033
    function HandlebarsEnvironment(helpers, partials) {
14034
      this.helpers = helpers || {};
14035
      this.partials = partials || {};
14036
14037
      registerDefaultHelpers(this);
14038
    }
14039
14040
    __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
14041
      constructor: HandlebarsEnvironment,
14042
14043
      logger: logger,
0 ignored issues
show
Bug introduced by
The variable logger seems to be never initialized.
Loading history...
14044
      log: log,
14045
14046
      registerHelper: function(name, fn, inverse) {
14047
        if (toString.call(name) === objectType) {
14048
          if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
14049
          Utils.extend(this.helpers, name);
14050
        } else {
14051
          if (inverse) { fn.not = inverse; }
14052
          this.helpers[name] = fn;
14053
        }
14054
      },
14055
14056
      registerPartial: function(name, str) {
14057
        if (toString.call(name) === objectType) {
14058
          Utils.extend(this.partials,  name);
14059
        } else {
14060
          this.partials[name] = str;
14061
        }
14062
      }
14063
    };
14064
14065
    function registerDefaultHelpers(instance) {
14066
      instance.registerHelper('helperMissing', function(arg) {
14067
        if(arguments.length === 2) {
14068
          return undefined;
14069
        } 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...
14070
          throw new Exception("Missing helper: '" + arg + "'");
14071
        }
14072
      });
14073
14074
      instance.registerHelper('blockHelperMissing', function(context, options) {
14075
        var inverse = options.inverse || function() {}, fn = options.fn;
14076
14077
        if (isFunction(context)) { context = context.call(this); }
14078
14079
        if(context === true) {
14080
          return fn(this);
14081
        } else if(context === false || context == null) {
0 ignored issues
show
Best Practice introduced by
Comparing context to null using the == operator is not safe. Consider using === instead.
Loading history...
14082
          return inverse(this);
14083
        } else if (isArray(context)) {
14084
          if(context.length > 0) {
14085
            return instance.helpers.each(context, options);
14086
          } 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...
14087
            return inverse(this);
14088
          }
14089
        } else {
14090
          return fn(context);
14091
        }
14092
      });
14093
14094
      instance.registerHelper('each', function(context, options) {
14095
        var fn = options.fn, inverse = options.inverse;
14096
        var i = 0, ret = "", data;
14097
14098
        if (isFunction(context)) { context = context.call(this); }
14099
14100
        if (options.data) {
14101
          data = createFrame(options.data);
14102
        }
14103
14104
        if(context && typeof context === 'object') {
14105
          if (isArray(context)) {
14106
            for(var j = context.length; i<j; i++) {
14107
              if (data) {
14108
                data.index = i;
14109
                data.first = (i === 0);
14110
                data.last  = (i === (context.length-1));
14111
              }
14112
              ret = ret + fn(context[i], { data: data });
0 ignored issues
show
Bug introduced by
The variable data does not seem to be initialized in case options.data on line 14100 is false. Are you sure this can never be the case?
Loading history...
14113
            }
14114
          } else {
14115
            for(var key in context) {
14116
              if(context.hasOwnProperty(key)) {
14117
                if(data) { 
14118
                  data.key = key; 
14119
                  data.index = i;
14120
                  data.first = (i === 0);
14121
                }
14122
                ret = ret + fn(context[key], {data: data});
14123
                i++;
14124
              }
14125
            }
14126
          }
14127
        }
14128
14129
        if(i === 0){
14130
          ret = inverse(this);
14131
        }
14132
14133
        return ret;
14134
      });
14135
14136
      instance.registerHelper('if', function(conditional, options) {
14137
        if (isFunction(conditional)) { conditional = conditional.call(this); }
14138
14139
        // Default behavior is to render the positive path if the value is truthy and not empty.
14140
        // The `includeZero` option may be set to treat the condtional as purely not empty based on the
14141
        // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
14142
        if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
14143
          return options.inverse(this);
14144
        } 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...
14145
          return options.fn(this);
14146
        }
14147
      });
14148
14149
      instance.registerHelper('unless', function(conditional, options) {
14150
        return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
14151
      });
14152
14153
      instance.registerHelper('with', function(context, options) {
14154
        if (isFunction(context)) { context = context.call(this); }
14155
14156
        if (!Utils.isEmpty(context)) return options.fn(context);
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...
Complexity Best Practice introduced by
There is no return statement if !Utils.isEmpty(context) 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...
14157
      });
14158
14159
      instance.registerHelper('log', function(context, options) {
14160
        var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
0 ignored issues
show
Best Practice introduced by
Comparing options.data.level to null using the != operator is not safe. Consider using !== instead.
Loading history...
14161
        instance.log(level, context);
14162
      });
14163
    }
14164
14165
    var logger = {
14166
      methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
14167
14168
      // State enum
14169
      DEBUG: 0,
14170
      INFO: 1,
14171
      WARN: 2,
14172
      ERROR: 3,
14173
      level: 3,
14174
14175
      // can be overridden in the host environment
14176
      log: function(level, obj) {
14177
        if (logger.level <= level) {
14178
          var method = logger.methodMap[level];
14179
          if (typeof console !== 'undefined' && console[method]) {
14180
            console[method].call(console, obj);
14181
          }
14182
        }
14183
      }
14184
    };
14185
    __exports__.logger = logger;
14186
    function log(level, obj) { logger.log(level, obj); }
14187
14188
    __exports__.log = log;var createFrame = function(object) {
14189
      var obj = {};
14190
      Utils.extend(obj, object);
14191
      return obj;
14192
    };
14193
    __exports__.createFrame = createFrame;
14194
  });
14195
define(
14196
  'handlebars/runtime',["./utils","./exception","./base","exports"],
14197 View Code Duplication
  function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14198
    
14199
    var Utils = __dependency1__;
14200
    var Exception = __dependency2__["default"];
14201
    var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
14202
    var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
14203
14204
    function checkRevision(compilerInfo) {
14205
      var compilerRevision = compilerInfo && compilerInfo[0] || 1,
14206
          currentRevision = COMPILER_REVISION;
14207
14208
      if (compilerRevision !== currentRevision) {
14209
        if (compilerRevision < currentRevision) {
14210
          var runtimeVersions = REVISION_CHANGES[currentRevision],
14211
              compilerVersions = REVISION_CHANGES[compilerRevision];
14212
          throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
14213
                "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
14214
        } else {
14215
          // Use the embedded version info since the runtime doesn't know about this revision yet
14216
          throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
14217
                "Please update your runtime to a newer version ("+compilerInfo[1]+").");
14218
        }
14219
      }
14220
    }
14221
14222
    __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
14223
14224
    function template(templateSpec, env) {
14225
      if (!env) {
14226
        throw new Exception("No environment passed to template");
14227
      }
14228
14229
      // Note: Using env.VM references rather than local var references throughout this section to allow
14230
      // for external users to override these as psuedo-supported APIs.
14231
      var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
14232
        var result = env.VM.invokePartial.apply(this, arguments);
14233
        if (result != null) { return result; }
0 ignored issues
show
Best Practice introduced by
Comparing result to null using the != operator is not safe. Consider using !== instead.
Loading history...
14234
14235
        if (env.compile) {
14236
          var options = { helpers: helpers, partials: partials, data: data };
14237
          partials[name] = env.compile(partial, { data: data !== undefined }, env);
14238
          return partials[name](context, options);
14239
        } 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...
14240
          throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
14241
        }
14242
      };
14243
14244
      // Just add water
14245
      var container = {
14246
        escapeExpression: Utils.escapeExpression,
14247
        invokePartial: invokePartialWrapper,
14248
        programs: [],
14249
        program: function(i, fn, data) {
14250
          var programWrapper = this.programs[i];
14251
          if(data) {
14252
            programWrapper = program(i, fn, data);
14253
          } else if (!programWrapper) {
14254
            programWrapper = this.programs[i] = program(i, fn);
14255
          }
14256
          return programWrapper;
14257
        },
14258
        merge: function(param, common) {
14259
          var ret = param || common;
14260
14261
          if (param && common && (param !== common)) {
14262
            ret = {};
14263
            Utils.extend(ret, common);
14264
            Utils.extend(ret, param);
14265
          }
14266
          return ret;
14267
        },
14268
        programWithDepth: env.VM.programWithDepth,
14269
        noop: env.VM.noop,
14270
        compilerInfo: null
14271
      };
14272
14273
      return function(context, options) {
14274
        options = options || {};
14275
        var namespace = options.partial ? options : env,
14276
            helpers,
14277
            partials;
14278
14279
        if (!options.partial) {
14280
          helpers = options.helpers;
14281
          partials = options.partials;
14282
        }
14283
        var result = templateSpec.call(
14284
              container,
14285
              namespace, context,
14286
              helpers,
0 ignored issues
show
Bug introduced by
The variable helpers does not seem to be initialized in case !options.partial on line 14279 is false. Are you sure the function call handles undefined variables?
Loading history...
14287
              partials,
0 ignored issues
show
Bug introduced by
The variable partials does not seem to be initialized in case !options.partial on line 14279 is false. Are you sure the function call handles undefined variables?
Loading history...
14288
              options.data);
14289
14290
        if (!options.partial) {
14291
          env.VM.checkRevision(container.compilerInfo);
14292
        }
14293
14294
        return result;
14295
      };
14296
    }
14297
14298
    __exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
14299
      var args = Array.prototype.slice.call(arguments, 3);
14300
14301
      var prog = function(context, options) {
14302
        options = options || {};
14303
14304
        return fn.apply(this, [context, options.data || data].concat(args));
14305
      };
14306
      prog.program = i;
14307
      prog.depth = args.length;
14308
      return prog;
14309
    }
14310
14311
    __exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
14312
      var prog = function(context, options) {
14313
        options = options || {};
14314
14315
        return fn(context, options.data || data);
14316
      };
14317
      prog.program = i;
14318
      prog.depth = 0;
14319
      return prog;
14320
    }
14321
14322
    __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
14323
      var options = { partial: true, helpers: helpers, partials: partials, data: data };
14324
14325
      if(partial === undefined) {
14326
        throw new Exception("The partial " + name + " could not be found");
14327
      } else if(partial instanceof Function) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if partial instanceof Function 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...
14328
        return partial(context, options);
14329
      }
14330
    }
14331
14332
    __exports__.invokePartial = invokePartial;function noop() { return ""; }
14333
14334
    __exports__.noop = noop;
14335
  });
14336
define(
14337
  'handlebars.runtime',["./handlebars/base","./handlebars/safe-string","./handlebars/exception","./handlebars/utils","./handlebars/runtime","exports"],
14338 View Code Duplication
  function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __exports__) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14339
    
14340
    /*globals Handlebars: true */
14341
    var base = __dependency1__;
14342
14343
    // Each of these augment the Handlebars object. No need to setup here.
14344
    // (This is done to easily share code between commonjs and browse envs)
14345
    var SafeString = __dependency2__["default"];
14346
    var Exception = __dependency3__["default"];
14347
    var Utils = __dependency4__;
14348
    var runtime = __dependency5__;
14349
14350
    // For compatibility and usage outside of module systems, make the Handlebars object a namespace
14351
    var create = function() {
14352
      var hb = new base.HandlebarsEnvironment();
14353
14354
      Utils.extend(hb, base);
14355
      hb.SafeString = SafeString;
14356
      hb.Exception = Exception;
14357
      hb.Utils = Utils;
14358
14359
      hb.VM = runtime;
14360
      hb.template = function(spec) {
14361
        return runtime.template(spec, hb);
14362
      };
14363
14364
      return hb;
14365
    };
14366
14367
    var Handlebars = create();
14368
    Handlebars.create = create;
14369
14370
    __exports__["default"] = Handlebars;
14371
  });define("bootstrap.wysihtml5.templates", ["handlebars.runtime", "wysihtml5"], function(HandlebarsEnv, wysihtml5) {
14372
	this["wysihtml5"] = wysihtml5;
14373
	var Handlebars = HandlebarsEnv.default;
14374
14375
this["wysihtml5"] = this["wysihtml5"] || {};
14376
this["wysihtml5"]["tpl"] = this["wysihtml5"]["tpl"] || {};
14377
14378
this["wysihtml5"]["tpl"]["blockquote"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14379
  this.compilerInfo = [4,'>= 1.0.0'];
14380
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14381
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14382
14383
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...
14384
  
14385
  var buffer = "", stack1;
14386
  buffer += "btn-"
14387
    + 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...
14388
  return buffer;
14389
  }
14390
14391
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...
14392
  
14393
  
14394
  return " \n      <span class=\"fa fa-quote-left\"></span>\n    ";
14395
  }
14396
14397
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...
14398
  
14399
  
14400
  return "\n      <span class=\"glyphicon glyphicon-quote\"></span>\n    ";
14401
  }
14402
14403
  buffer += "<li>\n  <a class=\"btn ";
14404
  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...
14405
  if(stack1 || stack1 === 0) { buffer += stack1; }
14406
  buffer += " btn-default\" data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"blockquote\" data-wysihtml5-display-format-name=\"false\" tabindex=\"-1\">\n    ";
14407
  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...
14408
  if(stack1 || stack1 === 0) { buffer += stack1; }
14409
  buffer += "\n  </a>\n</li>\n";
14410
  return buffer;
14411
  });
14412
14413
this["wysihtml5"]["tpl"]["color"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14414
  this.compilerInfo = [4,'>= 1.0.0'];
14415
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14416
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14417
14418
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...
14419
  
14420
  var buffer = "", stack1;
14421
  buffer += "btn-"
14422
    + 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...
14423
  return buffer;
14424
  }
14425
14426
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
14427
  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...
14428
  if(stack1 || stack1 === 0) { buffer += stack1; }
14429
  buffer += "\" data-toggle=\"dropdown\" tabindex=\"-1\">\n    <span class=\"current-color\">"
14430
    + 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...
14431
    + "</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\">"
14432
    + 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...
14433
    + "</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\">"
14434
    + 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...
14435
    + "</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\">"
14436
    + 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...
14437
    + "</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\">"
14438
    + 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...
14439
    + "</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\">"
14440
    + 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...
14441
    + "</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\">"
14442
    + 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...
14443
    + "</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\">"
14444
    + 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...
14445
    + "</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\">"
14446
    + 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...
14447
    + "</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\">"
14448
    + 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...
14449
    + "</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\">"
14450
    + 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...
14451
    + "</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\">"
14452
    + 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...
14453
    + "</a></li>\n  </ul>\n</li>\n";
14454
  return buffer;
14455
  });
14456
14457
this["wysihtml5"]["tpl"]["emphasis"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14458
  this.compilerInfo = [4,'>= 1.0.0'];
14459
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14460
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14461
14462
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...
14463
  
14464
  var buffer = "", stack1;
14465
  buffer += "btn-"
14466
    + 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...
14467
  return buffer;
14468
  }
14469
14470
function program3(depth0,data) {
14471
  
14472
  var buffer = "", stack1;
14473
  buffer += "\n    <a class=\"btn ";
14474
  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...
14475
  if(stack1 || stack1 === 0) { buffer += stack1; }
14476
  buffer += " btn-default\" data-wysihtml5-command=\"small\" title=\"CTRL+S\" tabindex=\"-1\">"
14477
    + 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...
14478
    + "</a>\n    ";
14479
  return buffer;
14480
  }
14481
14482
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14483
  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...
14484
  if(stack1 || stack1 === 0) { buffer += stack1; }
14485
  buffer += " btn-default\" data-wysihtml5-command=\"bold\" title=\"CTRL+B\" tabindex=\"-1\">"
14486
    + 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...
14487
    + "</a>\n    <a class=\"btn ";
14488
  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...
14489
  if(stack1 || stack1 === 0) { buffer += stack1; }
14490
  buffer += " btn-default\" data-wysihtml5-command=\"italic\" title=\"CTRL+I\" tabindex=\"-1\">"
14491
    + 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...
14492
    + "</a>\n    <a class=\"btn ";
14493
  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...
14494
  if(stack1 || stack1 === 0) { buffer += stack1; }
14495
  buffer += " btn-default\" data-wysihtml5-command=\"underline\" title=\"CTRL+U\" tabindex=\"-1\">"
14496
    + 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...
14497
    + "</a>\n    ";
14498
  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...
14499
  if(stack1 || stack1 === 0) { buffer += stack1; }
14500
  buffer += "\n  </div>\n</li>\n";
14501
  return buffer;
14502
  });
14503
14504
this["wysihtml5"]["tpl"]["font-styles"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14505
  this.compilerInfo = [4,'>= 1.0.0'];
14506
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14507
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14508
14509
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...
14510
  
14511
  var buffer = "", stack1;
14512
  buffer += "btn-"
14513
    + 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...
14514
  return buffer;
14515
  }
14516
14517
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...
14518
  
14519
  
14520
  return "\n      <span class=\"fa fa-font\"></span>\n    ";
14521
  }
14522
14523
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...
14524
  
14525
  
14526
  return "\n      <span class=\"glyphicon glyphicon-font\"></span>\n    ";
14527
  }
14528
14529
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
14530
  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...
14531
  if(stack1 || stack1 === 0) { buffer += stack1; }
14532
  buffer += "\" data-toggle=\"dropdown\">\n    ";
14533
  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...
14534
  if(stack1 || stack1 === 0) { buffer += stack1; }
14535
  buffer += "\n    <span class=\"current-font\">"
14536
    + 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...
14537
    + "</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\">"
14538
    + 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...
14539
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h1\" tabindex=\"-1\">"
14540
    + 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...
14541
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h2\" tabindex=\"-1\">"
14542
    + 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...
14543
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h3\" tabindex=\"-1\">"
14544
    + 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...
14545
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h4\" tabindex=\"-1\">"
14546
    + 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...
14547
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h5\" tabindex=\"-1\">"
14548
    + 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...
14549
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h6\" tabindex=\"-1\">"
14550
    + 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...
14551
    + "</a></li>\n  </ul>\n</li>\n";
14552
  return buffer;
14553
  });
14554
14555
this["wysihtml5"]["tpl"]["html"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14556
  this.compilerInfo = [4,'>= 1.0.0'];
14557
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14558
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14559
14560
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...
14561
  
14562
  var buffer = "", stack1;
14563
  buffer += "btn-"
14564
    + 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...
14565
  return buffer;
14566
  }
14567
14568
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...
14569
  
14570
  
14571
  return "\n        <span class=\"fa fa-pencil\"></span>\n      ";
14572
  }
14573
14574
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...
14575
  
14576
  
14577
  return "\n        <span class=\"glyphicon glyphicon-pencil\"></span>\n      ";
14578
  }
14579
14580
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14581
  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...
14582
  if(stack1 || stack1 === 0) { buffer += stack1; }
14583
  buffer += " btn-default\" data-wysihtml5-action=\"change_view\" title=\""
14584
    + 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...
14585
    + "\" tabindex=\"-1\">\n      ";
14586
  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...
14587
  if(stack1 || stack1 === 0) { buffer += stack1; }
14588
  buffer += "\n    </a>\n  </div>\n</li>\n";
14589
  return buffer;
14590
  });
14591
14592
this["wysihtml5"]["tpl"]["image"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14593
  this.compilerInfo = [4,'>= 1.0.0'];
14594
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14595
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14596
14597
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...
14598
  
14599
  
14600
  return "modal-sm";
14601
  }
14602
14603
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...
14604
  
14605
  var buffer = "", stack1;
14606
  buffer += "btn-"
14607
    + 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...
14608
  return buffer;
14609
  }
14610
14611
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...
14612
  
14613
  
14614
  return "\n      <span class=\"fa fa-file-image-o\"></span>\n    ";
14615
  }
14616
14617
function program7(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...
14618
  
14619
  
14620
  return "\n      <span class=\"glyphicon glyphicon-picture\"></span>\n    ";
14621
  }
14622
14623
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-image-modal modal fade\" data-wysihtml5-dialog=\"insertImage\">\n    <div class=\"modal-dialog ";
14624
  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...
14625
  if(stack1 || stack1 === 0) { buffer += stack1; }
14626
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14627
    + 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...
14628
    + "</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=\"#\">"
14629
    + 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...
14630
    + "</a>\n          <a class=\"btn btn-primary\" data-dismiss=\"modal\"  data-wysihtml5-dialog-action=\"save\" href=\"#\">"
14631
    + 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...
14632
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14633
  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...
14634
  if(stack1 || stack1 === 0) { buffer += stack1; }
14635
  buffer += " btn-default\" data-wysihtml5-command=\"insertImage\" title=\""
14636
    + 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...
14637
    + "\" tabindex=\"-1\">\n    ";
14638
  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...
14639
  if(stack1 || stack1 === 0) { buffer += stack1; }
14640
  buffer += "\n  </a>\n</li>\n";
14641
  return buffer;
14642
  });
14643
14644
this["wysihtml5"]["tpl"]["link"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14645
  this.compilerInfo = [4,'>= 1.0.0'];
14646
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14647
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14648
14649
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...
14650
  
14651
  
14652
  return "modal-sm";
14653
  }
14654
14655
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...
14656
  
14657
  var buffer = "", stack1;
14658
  buffer += "btn-"
14659
    + 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...
14660
  return buffer;
14661
  }
14662
14663
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...
14664
  
14665
  
14666
  return "\n      <span class=\"fa fa-share-square-o\"></span>\n    ";
14667
  }
14668
14669
function program7(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...
14670
  
14671
  
14672
  return "\n      <span class=\"glyphicon glyphicon-share\"></span>\n    ";
14673
  }
14674
14675
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-link-modal modal fade\" data-wysihtml5-dialog=\"createLink\">\n    <div class=\"modal-dialog ";
14676
  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...
14677
  if(stack1 || stack1 === 0) { buffer += stack1; }
14678
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14679
    + 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...
14680
    + "</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>"
14681
    + 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...
14682
    + "\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=\"#\">"
14683
    + 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...
14684
    + "</a>\n          <a href=\"#\" class=\"btn btn-primary\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"save\">"
14685
    + 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...
14686
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14687
  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...
14688
  if(stack1 || stack1 === 0) { buffer += stack1; }
14689
  buffer += " btn-default\" data-wysihtml5-command=\"createLink\" title=\""
14690
    + 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...
14691
    + "\" tabindex=\"-1\">\n    ";
14692
  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...
14693
  if(stack1 || stack1 === 0) { buffer += stack1; }
14694
  buffer += "\n  </a>\n</li>\n";
14695
  return buffer;
14696
  });
14697
14698
this["wysihtml5"]["tpl"]["lists"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14699
  this.compilerInfo = [4,'>= 1.0.0'];
14700
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14701
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14702
14703
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...
14704
  
14705
  var buffer = "", stack1;
14706
  buffer += "btn-"
14707
    + 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...
14708
  return buffer;
14709
  }
14710
14711
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...
14712
  
14713
  
14714
  return "\n      <span class=\"fa fa-list-ul\"></span>\n    ";
14715
  }
14716
14717
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...
14718
  
14719
  
14720
  return "\n      <span class=\"glyphicon glyphicon-list\"></span>\n    ";
14721
  }
14722
14723
function program7(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...
14724
  
14725
  
14726
  return "\n      <span class=\"fa fa-list-ol\"></span>\n    ";
14727
  }
14728
14729
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...
14730
  
14731
  
14732
  return "\n      <span class=\"glyphicon glyphicon-th-list\"></span>\n    ";
14733
  }
14734
14735
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...
14736
  
14737
  
14738
  return "\n      <span class=\"fa fa-outdent\"></span>\n    ";
14739
  }
14740
14741
function program13(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...
14742
  
14743
  
14744
  return "\n      <span class=\"glyphicon glyphicon-indent-right\"></span>\n    ";
14745
  }
14746
14747
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...
14748
  
14749
  
14750
  return "\n      <span class=\"fa fa-indent\"></span>\n    ";
14751
  }
14752
14753
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...
14754
  
14755
  
14756
  return "\n      <span class=\"glyphicon glyphicon-indent-left\"></span>\n    ";
14757
  }
14758
14759
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14760
  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...
14761
  if(stack1 || stack1 === 0) { buffer += stack1; }
14762
  buffer += " btn-default\" data-wysihtml5-command=\"insertUnorderedList\" title=\""
14763
    + 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...
14764
    + "\" tabindex=\"-1\">\n    ";
14765
  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...
14766
  if(stack1 || stack1 === 0) { buffer += stack1; }
14767
  buffer += "\n    </a>\n    <a class=\"btn ";
14768
  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...
14769
  if(stack1 || stack1 === 0) { buffer += stack1; }
14770
  buffer += " btn-default\" data-wysihtml5-command=\"insertOrderedList\" title=\""
14771
    + 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...
14772
    + "\" tabindex=\"-1\">\n    ";
14773
  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...
14774
  if(stack1 || stack1 === 0) { buffer += stack1; }
14775
  buffer += "\n    </a>\n    <a class=\"btn ";
14776
  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...
14777
  if(stack1 || stack1 === 0) { buffer += stack1; }
14778
  buffer += " btn-default\" data-wysihtml5-command=\"Outdent\" title=\""
14779
    + 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...
14780
    + "\" tabindex=\"-1\">\n    ";
14781
  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...
14782
  if(stack1 || stack1 === 0) { buffer += stack1; }
14783
  buffer += "\n    </a>\n    <a class=\"btn ";
14784
  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...
14785
  if(stack1 || stack1 === 0) { buffer += stack1; }
14786
  buffer += " btn-default\" data-wysihtml5-command=\"Indent\" title=\""
14787
    + 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...
14788
    + "\" tabindex=\"-1\">\n    ";
14789
  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...
14790
  if(stack1 || stack1 === 0) { buffer += stack1; }
14791
  buffer += "\n    </a>\n  </div>\n</li>\n";
14792
  return buffer;
14793
  });
14794
14795
});define("bootstrap.wysihtml5.commands", ["wysihtml5"], function(wysihtml5) {
14796
14797
(function(wysihtml5) {
14798
  wysihtml5.commands.small = {
14799
    exec: function(composer, command) {
14800
      return wysihtml5.commands.formatInline.exec(composer, command, "small");
14801
    },
14802
14803
    state: function(composer, command) {
14804
      return wysihtml5.commands.formatInline.state(composer, command, "small");
14805
    }
14806
  };
14807
})(wysihtml5);
14808
14809
14810
14811
});/* jshint expr: true */
14812
(function (factory) {
14813
    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...
14814
        // AMD. Register as an anonymous module.
14815
        define('bootstrap.wysihtml5', ['jquery', 'wysihtml5', 'bootstrap', 'bootstrap.wysihtml5.templates', 'bootstrap.wysihtml5.commands'], factory);
14816
    } else {
14817
        // Browser globals
14818
        factory(jQuery, wysihtml5);
0 ignored issues
show
Bug introduced by
The variable wysihtml5 seems to be never declared. If this is a global, consider adding a /** global: wysihtml5 */ 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...
14819
    }
14820 View Code Duplication
}(function ($, wysihtml5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14821
14822
var bsWysihtml5 = function($, wysihtml5) {
14823
  'use strict';
14824
14825
  var templates = function(key, locale, options) {
14826
    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...
14827
      return wysihtml5.tpl[key]({locale: locale, options: options});
14828
    }
14829
  };
14830
14831
  var Wysihtml5 = function(el, options) {
14832
    this.el = el;
14833
    var toolbarOpts = $.extend(true, {}, defaultOptions, options);
14834
    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...
14835
      wysihtml5.tpl[t] = toolbarOpts.customTemplates[t];
14836
    }
14837
    this.toolbar = this.createToolbar(el, toolbarOpts);
14838
    this.editor =  this.createEditor(toolbarOpts);
14839
  };
14840
14841
  Wysihtml5.prototype = {
14842
14843
    constructor: Wysihtml5,
14844
14845
    createEditor: function(options) {
14846
      options = options || {};
14847
14848
      // Add the toolbar to a clone of the options object so multiple instances
14849
      // of the WYISYWG don't break because 'toolbar' is already defined
14850
      options = $.extend(true, {}, options);
14851
      options.toolbar = this.toolbar[0];
14852
14853
      var editor = new wysihtml5.Editor(this.el[0], options);
14854
14855
      // #30 - body is in IE 10 not created by default, which leads to nullpointer
14856
      // 2014/02/13 - adapted to wysihtml5-0.4, does not work in IE
14857
      if(editor.composer.editableArea.contentDocument) {
14858
        this.addMoreShortcuts(editor, editor.composer.editableArea.contentDocument.body || editor.composer.editableArea.contentDocument, options.shortcuts);
14859
      } else {
14860
        this.addMoreShortcuts(editor, editor.composer.editableArea, options.shortcuts);    
14861
      }
14862
      
14863
14864
      if(options && options.events) {
14865
        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...
14866
          editor.on(eventName, options.events[eventName]);
14867
        }
14868
      }
14869
      
14870
      editor.on('beforeload', this.syncBootstrapDialogEvents);
14871
      //syncBootstrapDialogEvents();
14872
      return editor;
14873
    },
14874
14875
    //sync wysihtml5 events for dialogs with bootstrap events
14876
    syncBootstrapDialogEvents: function() {
14877
      var editor = this;
14878
      $.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...
14879
        return [value];
14880
      }).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...
14881
        return commandObj.dialog;
14882
      }).map(function(commandObj, idx, arr) {
0 ignored issues
show
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...
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...
14883
        return commandObj.dialog;
14884
      }).forEach(function(dialog, idx, arr) {
0 ignored issues
show
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...
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...
14885
        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...
14886
          $(this.container).modal('show');
14887
        });
14888
        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...
14889
          $(this.container).modal('hide');
14890
          editor.composer.focus();
14891
        });
14892
        $(dialog.container).on('shown.bs.modal', function () {
14893
          $(this).find('input, select, textarea').first().focus();
14894
        });
14895
      });
14896
    },
14897
14898
    createToolbar: function(el, options) {
14899
      var self = this;
14900
      var toolbar = $('<ul/>', {
14901
        'class' : 'wysihtml5-toolbar',
14902
        'style': 'display:none'
14903
      });
14904
      var culture = options.locale || defaultOptions.locale || 'en';
14905
      if(!locale.hasOwnProperty(culture)) {
14906
        console.debug('Locale \'' + culture + '\' not found. Available locales are: ' + Object.keys(locale) + '. Falling back to \'en\'.');
14907
        culture = 'en';
14908
      }
14909
      var localeObject = $.extend(true, {}, locale.en, locale[culture]);
14910
      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...
14911
        if(options.toolbar[key]) {
14912
          toolbar.append(templates(key, localeObject, options));
14913
14914
          if(key === 'html') {
14915
            this.initHtml(toolbar);
14916
          }
14917
14918
        }
14919
      }
14920
14921
      toolbar.find('a[data-wysihtml5-command="formatBlock"]').click(function(e) {
14922
        var target = e.delegateTarget || e.target || e.srcElement,
14923
            el = $(target),
14924
            showformat = el.data('wysihtml5-display-format-name'),
14925
            formatname = el.data('wysihtml5-format-name') || el.html();
14926
        if(showformat === undefined || showformat === 'true') {
14927
          self.toolbar.find('.current-font').text(formatname);
14928
        }
14929
      });
14930
14931
      toolbar.find('a[data-wysihtml5-command="foreColor"]').click(function(e) {
14932
        var target = e.target || e.srcElement;
14933
        var el = $(target);
14934
        self.toolbar.find('.current-color').text(el.html());
14935
      });
14936
14937
      this.el.before(toolbar);
14938
14939
      return toolbar;
14940
    },
14941
14942
    initHtml: function(toolbar) {
14943
      var changeViewSelector = 'a[data-wysihtml5-action="change_view"]';
14944
      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...
14945
        toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled');
14946
      });
14947
    },
14948
14949
    addMoreShortcuts: function(editor, el, shortcuts) {
14950
      /* some additional shortcuts */
14951
      wysihtml5.dom.observe(el, 'keydown', function(event) {
14952
        var keyCode  = event.keyCode,
14953
            command  = shortcuts[keyCode];
14954
        if ((event.ctrlKey || event.metaKey || event.altKey) && command && wysihtml5.commands[command]) {
14955
14956
          var commandObj = editor.toolbar.commandMapping[command + ':null'];
14957
          if (commandObj && commandObj.dialog && !commandObj.state) {
14958
            commandObj.dialog.show();
14959
          } else {
14960
            wysihtml5.commands[command].exec(editor.composer, command);
14961
          }
14962
          event.preventDefault();
14963
        }
14964
      });
14965
    }
14966
  };
14967
14968
  // these define our public api
14969
  var methods = {
14970
    resetDefaults: function() {
14971
      $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
14972
    },
14973
    bypassDefaults: function(options) {
14974
      return this.each(function () {
14975
        var $this = $(this);
14976
        $this.data('wysihtml5', new Wysihtml5($this, options));
14977
      });
14978
    },
14979
    shallowExtend: function (options) {
14980
      var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
14981
      var that = this;
14982
      return methods.bypassDefaults.apply(that, [settings]);
14983
    },
14984
    deepExtend: function(options) {
14985
      var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
14986
      var that = this;
14987
      return methods.bypassDefaults.apply(that, [settings]);
14988
    },
14989
    init: function(options) {
14990
      var that = this;
14991
      return methods.shallowExtend.apply(that, [options]);
14992
    }
14993
  };
14994
14995
  $.fn.wysihtml5 = function ( method ) {
14996
    if ( methods[method] ) {
14997
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
14998
    } else if ( typeof method === 'object' || ! method ) {
14999
      return methods.init.apply( this, arguments );
15000
    } else {
15001
      $.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...
15002
    }    
15003
  };
15004
15005
  $.fn.wysihtml5.Constructor = Wysihtml5;
15006
15007
  var defaultOptions = $.fn.wysihtml5.defaultOptions = {
15008
    toolbar: {
15009
      'font-styles': true,
15010
      'color': false,
15011
      'emphasis': {
15012
        'small': true
15013
      },
15014
      'blockquote': true,
15015
      'lists': true,
15016
      'html': false,
15017
      'link': true,
15018
      'image': true,
15019
      'smallmodals': false
15020
    },
15021
    parserRules: {
15022
      classes: {
15023
        'wysiwyg-color-silver' : 1,
15024
        'wysiwyg-color-gray' : 1,
15025
        'wysiwyg-color-white' : 1,
15026
        'wysiwyg-color-maroon' : 1,
15027
        'wysiwyg-color-red' : 1,
15028
        'wysiwyg-color-purple' : 1,
15029
        'wysiwyg-color-fuchsia' : 1,
15030
        'wysiwyg-color-green' : 1,
15031
        'wysiwyg-color-lime' : 1,
15032
        'wysiwyg-color-olive' : 1,
15033
        'wysiwyg-color-yellow' : 1,
15034
        'wysiwyg-color-navy' : 1,
15035
        'wysiwyg-color-blue' : 1,
15036
        'wysiwyg-color-teal' : 1,
15037
        'wysiwyg-color-aqua' : 1,
15038
        'wysiwyg-color-orange' : 1
15039
      },
15040
      tags: {
15041
        'b':  {},
15042
        'i':  {},
15043
        'strong': {},
15044
        'em': {},
15045
        'p': {},
15046
        'br': {},
15047
        'ol': {},
15048
        'ul': {},
15049
        'li': {},
15050
        'h1': {},
15051
        'h2': {},
15052
        'h3': {},
15053
        'h4': {},
15054
        'h5': {},
15055
        'h6': {},
15056
        'blockquote': {},
15057
        'u': 1,
15058
        'img': {
15059
          'check_attributes': {
15060
            'width': 'numbers',
15061
            'alt': 'alt',
15062
            'src': 'url',
15063
            'height': 'numbers'
15064
          }
15065
        },
15066
        'a':  {
15067
          check_attributes: {
15068
            'href': 'url' // important to avoid XSS
15069
          },
15070
          'set_attributes': {
15071
            'target': '_blank',
15072
            'rel': 'nofollow'
15073
          }
15074
        },
15075
        'span': 1,
15076
        'div': 1,
15077
        'small': 1,
15078
        // to allow save and edit files with code tag hacks
15079
        'code': 1,
15080
        'pre': 1
15081
      }
15082
    },
15083
    locale: 'en',
15084
    shortcuts: {
15085
      '83': 'small'     // S
15086
    }
15087
    
15088
  };
15089
15090
  if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
15091
    $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
15092
  }
15093
15094
  var locale = $.fn.wysihtml5.locale = {};
15095
};
15096
bsWysihtml5($, wysihtml5);
15097
}));
15098