Passed
Push — master ( 0561d0...39cfaa )
by Justin
04:49 queued 34s
created

styles/admin/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js   F

Complexity

Total Complexity 3735
Complexity/F 3.04

Size

Lines of Code 14970
Function Count 1227

Duplication

Duplicated Lines 339
Ratio 2.26 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 3735
c 1
b 1
f 0
dl 339
loc 14970
rs 2.4
cc 5
nc 0
mnd 7
bc 2972
fnc 1227
bpm 2.4221
cpm 3.044
noi 408

493 Functions

Rating   Name   Duplication   Size   Complexity  
A wysihtml5.browser.hasUndoInContextMenu 0 3 1
A wysihtml5.commands.alignRightStyle.exec 0 3 1
A api.table.orderSelectionEnds 0 4 1
A Object.defineProperty.set 0 3 1
A wysihtml5.commands.insertLineBreak.exec 0 10 3
A wysihtml5.dom.renameElement 0 10 2
A wysihtml5.browser.hasInsertNodeIssue 0 3 1
A wysihtml5.commands.removeLink.exec 0 8 2
A wysihtml5.browser.autoScrollsToCaret 0 3 1
A wysihtml5.browser.supportsSelectionModify 0 3 1
A wysihtml5.dom.parse 0 11 1
A TableModifyerByCell.splitRowToCells 0 11 3
A bootstrap3-wysihtml5.all.js ➔ ??? 0 1 3
B wysihtml5.lang.Dispatcher.extend.show 0 22 6
A wysihtml5.browser.doesAsyncFocus 0 3 1
A wysihtml5.dom.compareDocumentPosition.constructor 0 64 2
A bootstrap3-wysihtml5.all.js ➔ shouldIgnoreBoxSizingBorderBox 0 6 2
A Base.extend.createRange 0 3 1
A TableModifyerByCell.removeColumn 0 12 4
F wysihtml5.views.Composer.observe 0 231 18
A wysihtml5.views.View.extend.clear 0 3 2
A wysihtml5.commands.outdentList.exec 0 7 2
B wysihtml5.quirks.tableCellsSelection 0 117 1
B wysihtml5.toolbar.Speech 0 41 4
C HTMLApplier.undoToTextNode 0 29 13
C Base.extend.deblockAndSurround 0 36 7
B wysihtml5.dom.observe 0 45 4
B wysihtml5.commands.formatCode.exec 0 29 3
A wysihtml5.lang.Dispatcher.extend.enable 0 4 1
B bootstrap3-wysihtml5.all.js ➔ getMatchingStyleRegexp 0 23 6
A wysihtml5.commands.small.state 0 3 1
A a.toString 0 1 1
A wysihtml5.browser.isAndroid 0 3 1
B wysihtml5.commands.addTableCells.exec 0 15 8
A bootstrap3-wysihtml5.all.js ➔ _convertUrlsToLinks 0 23 1
A wysihtml5.browser.hasProblemsSettingCaretAfterImg 0 3 1
A wysihtml5.lang.Dispatcher.extend.undoPossible 0 3 1
B wysihtml5.commands.insertList.constructor 0 159 1
A wysihtml5.browser.doesAutoLinkingInContentEditable 0 3 1
A wysihtml5.dom.isLoadedImage 0 9 4
C bootstrap3-wysihtml5.all.js ➔ handleDeleteKeyPress 0 41 11
A bootstrap3-wysihtml5.all.js ➔ c 0 1 2
B TableModifyerByCell.getColumnElementsByCell 0 13 5
A d.constructor 0 1 1
A wysihtml5.commands.alignLeftStyle.state 0 3 1
A Base.extend.hide 0 3 1
D TableModifyerByCell.canMerge 0 27 9
A b.constructor 0 1 1
A Base.extend.getText 0 4 2
A wysihtml5.browser.autoClosesUnclosedTags 0 14 1
A bootstrap3-wysihtml5.all.js ➔ _isElement 0 3 1
A bootstrap3-wysihtml5.all.js ➔ createListFallback 0 23 1
B wysihtml5.dom.domNode 0 51 1
A wysihtml5.browser.canDisableAutoLinking 0 3 1
A a.constructor 0 1 1
A wysihtml5.views.View.extend.isEmpty 0 9 1
A wysihtml5.dom.hasElementWithTagName.constructor 0 18 1
A bootstrap3-wysihtml5.all.js ➔ _isSameNodeName 0 11 4
A bootstrap3-wysihtml5.all.js ➔ _createListItem 0 5 1
A bootstrap3-wysihtml5.all.js ➔ _getCumulativeOffsetTop 0 10 3
C TableModifyerByCell.merge 0 33 11
A bootstrap3-wysihtml5.all.js ➔ hasClass 0 8 2
A api.table.getCellsBetween 0 4 1
A wysihtml5.commands.italic.state 0 8 1
A Base.extend.setBefore 0 6 1
B Base.extend.surround 0 28 6
D TableModifyerByCell.addRow 0 33 9
A wysihtml5.views.View.extend.setValue 0 11 3
A wysihtml5.commands.outdentList.state 0 3 1
A Base.extend.deselect 0 4 1
B Base.extend._createIframe 0 33 2
A wysihtml5.dom.setAttributes 0 9 1
A wysihtml5.browser.hasHistoryIssue 0 3 1
A api.table.unmergeCell 0 4 1
A bootstrap3-wysihtml5.all.js ➔ handleOtherTypeList 0 16 2
A wysihtml5.commands.outdentList.getAfterList 0 9 2
A wysihtml5.commands.alignCenterStyle.exec 0 3 1
A api.table.addCells 0 4 1
D HTMLApplier.undoToRange 43 43 9
A wysihtml5.dom.getPastedHtml 0 11 4
F wysihtml5.views.View.extend._create 0 82 21
A Base.extend.findNodesInSelection 0 11 2
B TableModifyerByCell.removeRow 0 17 5
B wysihtml5.views.View.extend.focus 0 19 7
A wysihtml5.commands.createLink.state 0 3 1
A wysihtml5.commands.justifyCenter.exec 0 3 1
A wysihtml5.commands.alignLeftStyle.exec 0 3 1
A wysihtml5.lang.Dispatcher.extend.disable 0 4 1
A api.table.findColumnByCell 0 4 1
A Base.extend.insertHTML 0 18 3
A Base.extend.getRange 0 10 2
A wysihtml5.EMPTY_FUNCTION 0 1 1
B Base.extend.getSelectedNode 0 19 8
A dom.getTextContent 0 3 1
A wysihtml5.lang.Dispatcher.extend.focus 0 4 1
A bootstrap3-wysihtml5.all.js ➔ hasSameClasses 0 3 1
F wysihtml5.lang.Dispatcher.extend.transact 0 48 11
A Base.extend.stopObserving 0 3 1
A wysihtml5.quirks.cleanPastedHTML 0 19 1
A wysihtml5.views.View.extend._initUndoManager 0 3 1
C bootstrap3-wysihtml5.all.js ➔ resolveList 0 54 14
B bootstrap3-wysihtml5.all.js ➔ removeStyle 0 17 6
C Base.extend._selectLine_MSIE 0 46 7
A wysihtml5.commands.italic.exec 0 3 1
A bootstrap3-wysihtml5.all.js ➔ getListsInSelection 0 12 2
A TableModifyerByCell.getTableRows 0 8 3
A wysihtml5.browser.supportsSandboxedIframes 0 3 1
A HTMLApplier.isRemovable 0 8 1
A bootstrap3-wysihtml5.all.js ➔ addStyle 0 12 4
C wysihtml5.commands.formatBlock.exec 0 80 7
A Base.extend.fixSelBorders 0 5 1
A wysihtml5.commands.foreColor.state 0 3 1
A p.log 0 1 2
A wysihtml5.lang.Dispatcher.extend.isCompatible 0 3 1
B wysihtml5.commands.mergeTableCells.state 0 18 9
A wysihtml5.dom.getStyle.constructor 0 63 2
A bootstrap3-wysihtml5.all.js ➔ handleTabKeyDown 0 10 4
C wysihtml5.dom.getParentElement.constructor 0 52 1
A wysihtml5.commands.underline.state 0 3 1
A Base.extend._bindElement 0 5 3
A bootstrap3-wysihtml5.all.js ➔ handleSameTypeList 0 22 2
A bootstrap3-wysihtml5.all.js ➔ hasBoxSizingBorderBox 0 9 3
A wysihtml5.lang.Dispatcher.extend.clear 0 4 1
A TableModifyerByCell.getRowElementsByCell 0 14 4
F Base.extend._updateLinkStates 0 77 20
A wysihtml5.views.View.extend.hasPlaceholderSet 0 3 2
A Merge.toString 0 7 2
A wysihtml5.dom.getAsDom 0 15 4
B bootstrap3-wysihtml5.all.js ➔ d 0 1 5
A wysihtml5.dom.getStyle 0 50 1
D TableModifyerByCell.handleCellAddWithRowspan 0 37 9
A bootstrap3-wysihtml5.all.js ➔ l 0 1 1
A wysihtml5.commands.fontSize.exec 0 3 1
A wysihtml5.commands.foreColorStyle.state 3 3 1
A Base.extend.fromTextareaToComposer 0 9 2
B wysihtml5.lang.array 0 126 1
A api.table.removeCells 0 4 1
B wysihtml5.views.View.extend._initAutoLinking 0 82 6
D wysihtml5.views.Composer.style 0 102 9
A wysihtml5.commands.bold.exec 0 3 1
A bootstrap3-wysihtml5.all.js ➔ queryInList 0 11 4
B TableModifyerByCell.addSpannedCellToMap 0 22 6
A wysihtml5.commands.underline.exec 0 3 1
A bootstrap3-wysihtml5.all.js ➔ androidVersion 0 3 1
D wysihtml5.quirks.styleParser.parseColor 0 40 9
A c.registerPartial 0 1 2
A HTMLApplier.areElementsMergeable 0 6 1
A wysihtml5.lang.Dispatcher.extend.constructor 0 13 1
B wysihtml5.commands.mergeTableCells.exec 0 9 5
A wysihtml5.lang.Dispatcher.extend.parse 0 14 4
A Base.extend.sync 0 7 2
A wysihtml5.commands.addTableCells.state 0 3 1
B wysihtml5.dom.query 0 16 5
A wysihtml5.quirks.ensureProperClearing 0 3 1
A bootstrap3-wysihtml5.all.js ➔ HTMLApplier 0 10 2
A bootstrap3-wysihtml5.all.js ➔ styleToRegex 0 6 1
A bootstrap3-wysihtml5.all.js ➔ q 0 1 1
B dom.simulatePlaceholder 0 36 1
A wysihtml5.lang.Dispatcher.extend.getChildNodeIndex 0 10 3
A wysihtml5.dom.insert 0 15 1
A Base.extend.observe 0 3 1
A bootstrap3-wysihtml5.all.js ➔ removeElement 0 3 1
B Base.extend 0 43 2
C wysihtml5.lang.Dispatcher.extend._interpolate 0 27 8
B HTMLApplier.applyToTextNode 0 16 5
A TableModifyerByCell.remove 0 13 4
B bootstrap3-wysihtml5.all.js ➔ _hasParentThatShouldBeIgnored 0 16 6
B TableModifyerByCell.add 0 10 6
B bootstrap3-wysihtml5.all.js ➔ isNode 0 13 6
A wysihtml5.lang.string 0 54 1
C bootstrap3-wysihtml5.all.js ➔ isIE 0 22 12
A wysihtml5.browser.insertsLineBreaksOnReturn 0 3 1
A wysihtml5.browser.createsNestedInvalidMarkupAfterPaste 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _getTagNames 0 4 2
D bootstrap3-wysihtml5.all.js ➔ findListEl 0 36 10
A api.addClass 0 10 3
A wysihtml5.views.View.extend.enable 0 4 1
A Base.extend.destroy 0 4 1
A TableModifyerByCell.setCellAsModified 0 8 3
B Base.extend._unset 0 20 8
A wysihtml5.commands.insertList.state 0 6 3
B Base.extend.caretIsBeforeUneditable 0 18 5
A bootstrap3-wysihtml5.all.js ➔ cleanTempElements 0 6 2
B TableModifyerByCell.createCells 0 22 5
A c.constructor 0 1 1
A wysihtml5.browser.supportsNativeGetElementsByClassName 0 3 1
A TableModifyerByCell.removeEmptyTable 0 9 3
A bootstrap3-wysihtml5.all.js ➔ b 0 1 1
C HTMLApplier.applyToRange 37 37 8
A Base.extend._createElement 0 6 1
B wysihtml5.commands.foreColorStyle.stateValue 19 19 6
A Base.extend.getIframe 0 3 1
A wysihtml5.dom.delegate 0 14 1
A wysihtml5.dom.hasElementWithTagName 0 9 2
B bootstrap3-wysihtml5.all.js ➔ _changeLinks 0 19 5
A wysihtml5.views.View.extend._createWysiwygFormField 0 9 2
B wysihtml5.dom.getAttribute 0 21 7
B wysihtml5.commands.deleteTableCells.exec 0 34 4
A wysihtml5.commands.bold.state 0 8 1
A Merge.doMerge 0 16 4
B wysihtml5.commands.formatCode.state 0 9 7
B TableModifyerByCell.collapseCellToNextRow 0 28 6
A Base.extend.focus 0 7 3
A wysihtml5.quirks.styleParser.parseFontSize 0 7 2
A wysihtml5.lang.Dispatcher.extend.isEmpty 0 3 1
A f.constructor 0 1 1
A bootstrap3-wysihtml5.all.js ➔ nextNode 0 10 4
A TableModifyerByCell.decreaseCellSpan 0 19 4
A HTMLApplier.getTextSelectedByRange 0 10 2
A wysihtml5.quirks.cleanPastedHTML.constructor 0 71 1
A wysihtml5.dom.unwrap 0 8 3
A api.removeClass 0 8 2
A wysihtml5.commands.createLink.exec 0 13 3
A bootstrap3-wysihtml5.all.js ➔ clearIfNecessary 0 10 1
A wysihtml5.browser.supportsPlaceholderAttributeOn 0 3 1
B Base.extend._getLinks 0 27 2
C wysihtml5.dom.getAttributes 0 23 13
A wysihtml5.browser.supportsModenPaste 0 3 1
A api.table.findCell 0 4 1
A bootstrap3-wysihtml5.all.js ➔ _hasStyles 0 3 1
B bootstrap3-wysihtml5.all.js ➔ e 0 1 5
A wysihtml5.commands.redo.exec 0 3 1
A HTMLApplier.getMatchingAncestor 0 20 4
A wysihtml5.views.View.extend._initContentEditableArea 0 16 2
A bootstrap3-wysihtml5.all.js ➔ expandRangeToSurround 0 19 4
B HTMLApplier.toggleRange 0 24 6
B TableModifyerByCell.orderSelectionEnds 0 22 5
A bootstrap3-wysihtml5.all.js ➔ removeOrChangeStyle 0 13 2
B wysihtml5.dom.getParentElement 0 17 12
A Base.extend.getNodes 0 8 2
C TableModifyerByCell.unmerge 0 23 7
A Base.extend.scrollIntoView 0 6 1
A wysihtml5.browser.isTouchDevice 0 3 1
A Merge.getLength 0 7 2
A Base.extend.insertNode 0 6 2
D bootstrap3-wysihtml5.all.js ➔ deleteAroundEditable 0 33 10
A wysihtml5.commands.justifyFull.state 0 3 1
A Base.extend.setAfter 0 7 1
B TableModifyerByCell.getRealRowEl 0 22 6
A Base.extend.state 0 16 3
A bootstrap3-wysihtml5.all.js ➔ _wrapMatchesInNode 0 16 2
A wysihtml5.quirks.redraw 0 11 2
A Base.extend.getOwnUneditables 0 6 1
A wysihtml5.browser.hasIframeFocusIssue 0 3 1
A wysihtml5.views.View.extend.disable 0 4 1
A wysihtml5.commands.justifyLeft.state 0 3 1
A wysihtml5.lang.Dispatcher.extend._observe 0 47 1
A Base.extend.selectLine 0 7 3
B bootstrap3-wysihtml5.all.js ➔ _execCommand 0 26 4
B HTMLApplier.getAdjacentMergeableTextNode 0 20 9
A wysihtml5.views.View.extend.hide 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _hasClassName 0 7 2
A wysihtml5.views.View.extend.cleanUp 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _isBlockElement 0 3 1
A TableModifyerByCell.correctColIndexForUnreals 0 10 3
A Base.extend._endOffsetForNode 0 5 1
A api.table.canMerge 0 4 1
A wysihtml5.commands.redo.state 0 3 1
A bootstrap3-wysihtml5.all.js ➔ getDepth 0 10 3
A wysihtml5.commands.createTable.state 0 3 1
A wysihtml5.commands.justifyRight.state 0 3 1
A bootstrap3-wysihtml5.all.js ➔ h 0 1 3
A bootstrap3-wysihtml5.all.js ➔ _createList 0 3 1
A Base.extend.implement 0 12 3
B bootstrap3-wysihtml5.all.js ➔ elementsHaveSameNonClassAttributes 0 19 7
A TableModifyerByCell.rectify 0 9 2
A Base.extend.caretIsInTheBeginnig 0 10 2
A wysihtml5.quirks.getCorrectInnerHTML 0 18 3
A Base.extend.disable 0 3 1
A wysihtml5.views.View.extend.getValue 0 8 4
A wysihtml5.dom.hasElementWithClassName 0 15 3
A wysihtml5.lang.Dispatcher.extend.cleanUp 0 3 1
A Base.extend._execCommand 0 7 1
A wysihtml5.dom.contains.constructor 0 16 3
B Base.extend.getSelectionParentsByTag 0 12 6
A wysihtml5.commands.alignCenterStyle.state 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _getApplier 0 16 4
A TableModifyerByCell.removeRowCell 0 15 4
A bootstrap3-wysihtml5.all.js ➔ isSplitPoint 0 13 4
B wysihtml5.browser.constructor 0 391 1
D Base.extend.getPreviousNode 0 36 19
A wysihtml5.commands.insertUnorderedList.state 0 3 1
A wysihtml5.lang.Dispatcher.extend.getValue 0 3 1
C wysihtml5.commands.insertImage.state 0 40 7
A api.table.indexOf 0 5 1
A bootstrap3-wysihtml5.all.js ➔ replaceWithOwnChildren 0 7 2
A wysihtml5.commands.fontSizeStyle.exec 0 6 3
A Base.extend.caretIsLastInSelection 0 8 1
A Base.extend.caretIsFirstInSelection 0 14 2
A Base.extend._detectInlineRangeProblems 0 7 1
A wysihtml5.commands.undo.state 0 3 1
A wysihtml5.commands.small.exec 0 3 1
B wysihtml5.commands.insertList.exec 0 18 5
A bootstrap3-wysihtml5.all.js ➔ g 0 1 1
A bootstrap3-wysihtml5.all.js ➔ MapCell 0 12 1
A Base.extend.deleteContents 0 7 2
A bootstrap3-wysihtml5.all.js ➔ _removeFormat 0 21 4
A wysihtml5.dom.getPastedHtmlWithDiv 0 20 1
A wysihtml5.toolbar.Dialog.extend.show 0 4 1
A wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty 0 3 1
B HTMLApplier.selectNode 0 18 10
A wysihtml5.commands.deleteTableCells.state 0 3 1
A bootstrap3-wysihtml5.all.js ➔ hasStyleAttr 0 7 4
A wysihtml5.browser.supportsMutationEvents 0 3 1
A wysihtml5.views.View.extend._initSandbox 0 15 1
A wysihtml5.lang.Dispatcher.extend._cleanAndPaste 0 9 1
A wysihtml5.browser.hasCurrentStyleProperty 0 3 1
A wysihtml5.browser.supportsEventsInIframeCorrectly 0 3 1
A Base.extend.stateValue 0 11 2
B bootstrap3-wysihtml5.all.js ➔ ?!? 0 25 4
A wysihtml5.commands.foreColor.exec 0 3 1
C wysihtml5.commands.createTable.exec 0 23 9
A Base.extend.getDocument 0 3 1
A Base.extend._selectLine_W3C 0 6 1
A Base.extend.setBookmark 0 7 2
A wysihtml5.commands.justifyCenter.state 0 3 1
B wysihtml5.commands.insertBlockQuote.exec 0 26 1
B wysihtml5.dom.convertToList.constructor 0 80 1
A Base.extend.isCollapsed 0 3 1
A wysihtml5.dom.copyAttributes 0 20 1
B wysihtml5.commands.formatInline.exec 0 27 5
A wysihtml5.dom.setStyles 0 19 1
A wysihtml5.lang.Dispatcher.extend._serialize 0 11 2
A bootstrap3-wysihtml5.all.js ➔ 0 16 4
A Base.extend.fire 0 9 2
A wysihtml5.commands.foreColorStyle.exec 12 12 4
C TableModifyerByCell.setTableMap 0 34 8
A bootstrap3-wysihtml5.all.js ➔ _hasClasses 0 3 1
D TableModifyerByCell.removeSurplusLines 0 39 10
B bootstrap3-wysihtml5.all.js ➔ pickRuleset 0 18 6
A wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly 0 3 1
A api.table.findRowByCell 0 4 1
C TableModifyerByCell.addColumn 0 22 7
A wysihtml5.commands.removeLink.state 0 3 1
A wysihtml5.browser.canSelectImagesInContentEditable 0 3 1
B wysihtml5.commands.fontSizeStyle.stateValue 0 16 5
A Base.extend.setSelection 0 5 1
A wysihtml5.commands.insertHTML.exec 0 7 2
A wysihtml5.browser.supportsSpeechApiOn 0 4 1
B wysihtml5.quirks.styleParser.unparseColor 0 21 9
A Base.extend.on 0 6 1
A bootstrap3-wysihtml5.all.js ➔ Base 0 3 1
A wysihtml5.commands.indentList.exec 0 7 2
A Base.extend.getBookmark 0 5 2
B TableModifyerByCell.getLastNewCellOnRow 0 13 5
A Base.extend.forEach 0 7 3
B bootstrap3-wysihtml5.all.js ➔ focusWithoutScrolling 0 36 4
A 0 13 1
A bootstrap3-wysihtml5.all.js ➔ _isLineBreak 0 3 1
A wysihtml5.commands.insertBlockQuote.state 0 6 2
A bootstrap3-wysihtml5.all.js ➔ param_REGX 0 3 1
A bootstrap3-wysihtml5.all.js ➔ autoLink 0 11 3
A wysihtml5.lang.Dispatcher.extend.getChildNodeByIndex 0 3 1
C TableModifyerByCell.addColCell 0 36 7
A e.constructor 0 1 1
A wysihtml5.commands.undo.exec 0 3 1
A wysihtml5.commands.insertUnorderedList.exec 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _getDocumentIdentifier 0 3 1
A bootstrap3-wysihtml5.all.js ➔ areMatchingAllready 0 8 4
A bootstrap3-wysihtml5.all.js ➔ _hasStyle 0 7 2
A bootstrap3-wysihtml5.all.js ➔ removeClass 0 5 2
A Base.extend.executeAndRestoreRangy 0 15 3
A wysihtml5.commands.bgColorStyle.state 3 3 1
D wysihtml5.commands.formatInline.state 0 26 9
A wysihtml5.commands.insertHTML.state 0 3 1
C wysihtml5.commands.insertImage.exec 0 41 8
A wysihtml5.browser.crashesWhenDefineProperty 0 3 1
A wysihtml5.commands.outdentList.tryToPullLiLevel 0 53 1
A wysihtml5.commands.bgColorStyle.exec 12 12 4
C bootstrap3-wysihtml5.all.js ➔ convertToList 0 66 17
A bootstrap3-wysihtml5.all.js ➔ _getTempElement 0 7 2
A wysihtml5.lang.Dispatcher.extend.handleBeforeLoad 0 8 3
A dom.setTextContent 0 3 1
A wysihtml5.commands.insertLineBreak.state 0 3 1
A Base.extend.constructor 0 3 1
A Base.extend.getWindow 0 3 1
A wysihtml5.browser.supportsGetAttributeCorrectly 0 4 1
A Base.extend._readyError 0 3 1
A Base.extend._loadElement 0 22 2
C HTMLApplier.isAppliedToRange 0 40 11
B wysihtml5.dom.insertCSS 0 27 1
A wysihtml5.lang.Dispatcher.extend.hasPlaceholderSet 0 3 1
A c.registerHelper 0 1 4
A Base.extend.getHtml 0 3 1
A wysihtml5.dom.removeEmptyTextNodes 0 12 4
A wysihtml5.lang.Dispatcher.extend.hide 0 7 1
A wysihtml5.browser.supportsHTML5Tags 0 6 1
A bootstrap3-wysihtml5.all.js ➔ i 0 1 1
B Base.extend.execAction 0 15 5
A Base.extend.show 0 3 1
A bootstrap3-wysihtml5.all.js ➔ insertAfter 0 3 1
B bootstrap3-wysihtml5.all.js ➔ extendRulesWithStyleExceptions 0 21 7
B dom.copyStyles 0 25 1
A wysihtml5.commands.fontSizeStyle.state 0 3 1
A HTMLApplier.createContainer 0 10 3
A wysihtml5.commands.alignRightStyle.state 0 3 1
A api.hasClass 0 9 2
B Base.extend.off 0 20 5
B wysihtml5.commands.bgColorStyle.stateValue 18 18 5
B wysihtml5.lang.Dispatcher.extend._initParser 0 26 2
A Base.extend.set 0 5 1
F Handlebars.template 34 34 13
B Base.extend.execCommand 0 14 5
A bootstrap3-wysihtml5.all.js ➔ iosVersion 0 3 1
A wysihtml5.commands.justifyFull.exec 0 3 1
A wysihtml5.browser.supported 0 15 1
B wysihtml5.views.View.extend._initLineBreaking 0 88 2
A TableModifyerByCell.injectRowAt 0 17 3
A Base.extend._observeViewChange 0 15 1
A api.table.mergeCellsBetween 0 4 1
A wysihtml5.views.View.extend.getTextContent 0 3 1
A Base.extend.exec 0 21 3
C HTMLApplier.postApply 0 55 14
A wysihtml5.commands.insertOrderedList.state 0 3 1
A wysihtml5.browser.isIos 0 3 1
A wysihtml5.lang.Dispatcher.extend.setValue 0 10 2
A bootstrap3-wysihtml5.all.js ➔ _innerHTMLShiv 0 9 2
A wysihtml5.views.View.extend.show 0 9 3
A TableModifyerByCell.removeColCell 0 11 4
A Array.isArray 0 3 1
A wysihtml5.dom.lineBreaks 0 59 1
D TableModifyerByCell.getMapElsTo 0 27 9
A wysihtml5.browser.clearsContentEditableCorrectly 0 3 1
A bootstrap3-wysihtml5.all.js ➔ _removeClass 0 8 2
B TableModifyerByCell.addRowCell 0 16 9
D Base.extend.executeAndRestore 0 79 13
A wysihtml5.lang.object 0 68 1
A Base.extend.getContentEditable 0 3 1
B wysihtml5.commands.formatBlock.state 0 25 6
D bootstrap3-wysihtml5.all.js ➔ splitNodeAt 0 30 10
B Base.extend._observe 0 37 2
A bootstrap3-wysihtml5.all.js ➔ TableModifyerByCell 0 9 3
A Base.extend.getSelection 0 3 1
A wysihtml5.browser.supportsEvent 0 4 1
A Base.extend.support 0 3 1
A bootstrap3-wysihtml5.all.js ➔ addClass 0 8 2
A wysihtml5.commands.justifyLeft.exec 0 3 1
A Base.extend.insertInto 0 7 2
A wysihtml5.toolbar.Dialog.extend._serialize 0 11 2
A bootstrap3-wysihtml5.all.js ➔ Merge 0 5 2
A wysihtml5.commands.justifyRight.exec 0 3 1
A wysihtml5.views.View.extend.constructor 0 13 3
A TableModifyerByCell.getRowCells 0 8 3
C Base.extend.fixRangeOverflow 0 22 10
A bootstrap3-wysihtml5.all.js ➔ _removeLastChildIfLineBreak 0 6 3
B wysihtml5.dom.getTextNodes 0 13 5
A Base.extend._getHtml 0 20 4
A wysihtml5.lang.Dispatcher.extend.redoPossible 0 3 1
A Object.defineProperty.get 0 3 1
A TableModifyerByCell.getElementAtIndex 0 7 4
C Base.extend.selectNode 0 29 14
A bootstrap3-wysihtml5.all.js ➔ camelize 0 5 1
A bootstrap3-wysihtml5.all.js ➔ _addStyle 0 8 2
A bootstrap3-wysihtml5.all.js ➔ _removeStyle 0 8 2
A bootstrap3-wysihtml5.all.js ➔ _ensureHTML5Compatibility 0 9 3
A bootstrap3-wysihtml5.all.js ➔ _selectionWrap 0 14 3
B Base.extend._getDialog 0 35 3
A bootstrap3-wysihtml5.all.js ➔ a 0 1 1
B HTMLApplier.getAncestorWithStyle 0 12 7
A wysihtml5.dom.getAsDom.constructor 0 52 1
A bootstrap3-wysihtml5.all.js ➔ _addClass 0 8 2
C wysihtml5.toolbar.Dialog.extend._interpolate 0 35 13
A bootstrap3-wysihtml5.all.js ➔ _appendLineBreak 0 4 1
B TableModifyerByCell.getMapIndex 0 13 6
C bootstrap3-wysihtml5.all.js ➔ _parseNode 0 24 7
D Base.extend.isEndToEndInNode 0 41 17
A Base.extend.fromComposerToTextarea 0 3 1
B Base.extend._onLoadIframe 0 57 6
A wysihtml5.commands.fontSize.state 0 3 1
B wysihtml5.lang.Dispatcher.extend.set 0 34 4
A bootstrap3-wysihtml5.all.js ➔ f 0 1 1
B wysihtml5.views.View.extend._observe 0 24 2
A wysihtml5.dom.replaceWithChildNodes 0 17 4
B HTMLApplier.getAncestorWithClass 0 11 8
C wysihtml5.commands.formatInline.execWithToggle 0 24 7
A wysihtml5.commands.indentList.state 0 3 1
A wysihtml5.lang.Dispatcher.extend.undo 0 10 2
B wysihtml5.commands.indentList.tryToPushLiLevel 0 27 1
A wysihtml5.quirks.ensureProperClearing.constructor 0 16 1
A wysihtml5.commands.insertOrderedList.exec 0 3 1
A Base.extend.getRangeToNodeEnd 0 12 2
C bootstrap3-wysihtml5.all.js ➔ _format 0 43 7
C Base.extend.getOwnRanges 0 41 12
A wysihtml5.lang.Dispatcher.extend.redo 0 8 2
A wysihtml5.browser.needsSpaceAfterLineBreak 0 3 1
C TableModifyerByCell.fillMissingCells 0 29 12
A Handlebars.constructor 0 1 1
A bootstrap3-wysihtml5.all.js ➔ isMatchingAllready 0 9 3
A Base.extend.getSelectedOwnNodes 0 10 2
B wysihtml5.views.View.extend._initObjectResizing 0 34 2
A Base.extend.toString 0 3 1
A Base.extend.containsUneditable 0 12 3
A Base.extend.enable 0 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 styles/admin/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

Consider:

if (a > 0)
    b = 42;

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

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

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

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

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

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

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

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

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

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

Consider:

if (a > 0)
    b = 42;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Consider this little piece of code

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

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

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

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

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

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

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

Loading history...
4310
    },
4311
4312
    /**
4313
     * Whether the browser inserts a <br> when pressing enter in a contentEditable element
4314
     */
4315
    insertsLineBreaksOnReturn: function() {
4316
      return isGecko;
4317
    },
4318
4319
    supportsPlaceholderAttributeOn: function(element) {
4320
      return "placeholder" in element;
4321
    },
4322
4323
    supportsEvent: function(eventName) {
4324
      return "on" + eventName in testElement || (function() {
4325
        testElement.setAttribute("on" + eventName, "return;");
4326
        return typeof(testElement["on" + eventName]) === "function";
4327
      })();
4328
    },
4329
4330
    /**
4331
     * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
4332
     */
4333
    supportsEventsInIframeCorrectly: function() {
4334
      return !isOpera;
4335
    },
4336
4337
    /**
4338
     * Everything below IE9 doesn't know how to treat HTML5 tags
4339
     *
4340
     * @param {Object} context The document object on which to check HTML5 support
4341
     *
4342
     * @example
4343
     *    wysihtml5.browser.supportsHTML5Tags(document);
4344
     */
4345
    supportsHTML5Tags: function(context) {
4346
      var element = context.createElement("div"),
4347
          html5   = "<article>foo</article>";
4348
      element.innerHTML = html5;
4349
      return element.innerHTML.toLowerCase() === html5;
4350
    },
4351
4352
    /**
4353
     * Checks whether a document supports a certain queryCommand
4354
     * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
4355
     * in oder to report correct results
4356
     *
4357
     * @param {Object} doc Document object on which to check for a query command
0 ignored issues
show
Documentation introduced by
The parameter doc does not exist. Did you maybe forget to remove this comment?
Loading history...
4358
     * @param {String} command The query command to check for
0 ignored issues
show
Documentation introduced by
The parameter command does not exist. Did you maybe forget to remove this comment?
Loading history...
4359
     * @return {Boolean}
4360
     *
4361
     * @example
4362
     *    wysihtml5.browser.supportsCommand(document, "bold");
4363
     */
4364
    supportsCommand: (function() {
4365
      // Following commands are supported but contain bugs in some browsers
4366
      var buggyCommands = {
4367
        // formatBlock fails with some tags (eg. <blockquote>)
4368
        "formatBlock":          isIE(10, "<="),
4369
         // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
4370
         // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
4371
         // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
4372
        "insertUnorderedList":  isIE(),
4373
        "insertOrderedList":    isIE()
4374
      };
4375
4376
      // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
4377
      var supported = {
4378
        "insertHTML": isGecko
4379
      };
4380
4381
      return function(doc, command) {
4382
        var isBuggy = buggyCommands[command];
4383
        if (!isBuggy) {
4384
          // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
4385
          try {
4386
            return doc.queryCommandSupported(command);
4387
          } catch(e1) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
4388
4389
          try {
4390
            return doc.queryCommandEnabled(command);
4391
          } catch(e2) {
4392
            return !!supported[command];
4393
          }
4394
        }
4395
        return false;
4396
      };
4397
    })(),
4398
4399
    /**
4400
     * IE: URLs starting with:
4401
     *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
4402
     *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
4403
     * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
4404
     * space bar when the caret is directly after such an url.
4405
     * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
4406
     * (related blog post on msdn
4407
     * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
4408
     */
4409
    doesAutoLinkingInContentEditable: function() {
4410
      return isIE();
4411
    },
4412
4413
    /**
4414
     * As stated above, IE auto links urls typed into contentEditable elements
4415
     * Since IE9 it's possible to prevent this behavior
4416
     */
4417
    canDisableAutoLinking: function() {
4418
      return this.supportsCommand(document, "AutoUrlDetect");
4419
    },
4420
4421
    /**
4422
     * IE leaves an empty paragraph in the contentEditable element after clearing it
4423
     * Chrome/Safari sometimes an empty <div>
4424
     */
4425
    clearsContentEditableCorrectly: function() {
4426
      return isGecko || isOpera || isWebKit;
4427
    },
4428
4429
    /**
4430
     * IE gives wrong results for getAttribute
4431
     */
4432
    supportsGetAttributeCorrectly: function() {
4433
      var td = document.createElement("td");
4434
      return td.getAttribute("rowspan") != "1";
4435
    },
4436
4437
    /**
4438
     * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
4439
     * Chrome and Safari both don't support this
4440
     */
4441
    canSelectImagesInContentEditable: function() {
4442
      return isGecko || isIE() || isOpera;
4443
    },
4444
4445
    /**
4446
     * All browsers except Safari and Chrome automatically scroll the range/caret position into view
4447
     */
4448
    autoScrollsToCaret: function() {
4449
      return !isWebKit;
4450
    },
4451
4452
    /**
4453
     * Check whether the browser automatically closes tags that don't need to be opened
4454
     */
4455
    autoClosesUnclosedTags: function() {
4456
      var clonedTestElement = testElement.cloneNode(false),
4457
          returnValue,
4458
          innerHTML;
4459
4460
      clonedTestElement.innerHTML = "<p><div></div>";
4461
      innerHTML                   = clonedTestElement.innerHTML.toLowerCase();
4462
      returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
4463
4464
      // Cache result by overwriting current function
4465
      this.autoClosesUnclosedTags = function() { return returnValue; };
4466
4467
      return returnValue;
4468
    },
4469
4470
    /**
4471
     * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
4472
     */
4473
    supportsNativeGetElementsByClassName: function() {
4474
      return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
4475
    },
4476
4477
    /**
4478
     * As of now (19.04.2011) only supported by Firefox 4 and Chrome
4479
     * See https://developer.mozilla.org/en/DOM/Selection/modify
4480
     */
4481
    supportsSelectionModify: function() {
4482
      return "getSelection" in window && "modify" in window.getSelection();
4483
    },
4484
4485
    /**
4486
     * Opera needs a white space after a <br> in order to position the caret correctly
4487
     */
4488
    needsSpaceAfterLineBreak: function() {
4489
      return isOpera;
4490
    },
4491
4492
    /**
4493
     * Whether the browser supports the speech api on the given element
4494
     * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
4495
     *
4496
     * @example
4497
     *    var input = document.createElement("input");
4498
     *    if (wysihtml5.browser.supportsSpeechApiOn(input)) {
4499
     *      // ...
4500
     *    }
4501
     */
4502
    supportsSpeechApiOn: function(input) {
4503
      var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
4504
      return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
4505
    },
4506
4507
    /**
4508
     * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
4509
     * See https://connect.microsoft.com/ie/feedback/details/650112
4510
     * or try the POC http://tifftiff.de/ie9_crash/
4511
     */
4512
    crashesWhenDefineProperty: function(property) {
4513
      return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
4514
    },
4515
4516
    /**
4517
     * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
4518
     */
4519
    doesAsyncFocus: function() {
4520
      return isIE();
4521
    },
4522
4523
    /**
4524
     * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
4525
     */
4526
    hasProblemsSettingCaretAfterImg: function() {
4527
      return isIE();
4528
    },
4529
4530
    hasUndoInContextMenu: function() {
4531
      return isGecko || isChrome || isOpera;
4532
    },
4533
4534
    /**
4535
     * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
4536
     * is used (regardless if rangy or native)
4537
     * This especially happens when the caret is positioned right after a <br> because then
4538
     * insertNode() will insert the node right before the <br>
4539
     */
4540
    hasInsertNodeIssue: function() {
4541
      return isOpera;
4542
    },
4543
4544
    /**
4545
     * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
4546
     */
4547
    hasIframeFocusIssue: function() {
4548
      return isIE();
4549
    },
4550
4551
    /**
4552
     * Chrome + Safari create invalid nested markup after paste
4553
     *
4554
     *  <p>
4555
     *    foo
4556
     *    <p>bar</p> <!-- BOO! -->
4557
     *  </p>
4558
     */
4559
    createsNestedInvalidMarkupAfterPaste: function() {
4560
      return isWebKit;
4561
    },
4562
4563
    supportsMutationEvents: function() {
4564
        return ("MutationEvent" in window);
4565
    },
4566
4567
    /**
4568
      IE (at least up to 11) does not support clipboardData on event.
4569
      It is on window but cannot return text/html
4570
      Should actually check for clipboardData on paste event, but cannot in firefox
4571
    */
4572
    supportsModenPaste: function () {
4573
      return !("clipboardData" in window);
4574
    }
4575
  };
4576
})();
4577
;wysihtml5.lang.array = function(arr) {
4578
  return {
4579
    /**
4580
     * Check whether a given object exists in an array
4581
     *
4582
     * @example
4583
     *    wysihtml5.lang.array([1, 2]).contains(1);
4584
     *    // => true
4585
     *
4586
     * Can be used to match array with array. If intersection is found true is returned
4587
     */
4588
    contains: function(needle) {
4589
      if (Array.isArray(needle)) {
4590
        for (var i = needle.length; i--;) {
4591
          if (wysihtml5.lang.array(arr).indexOf(needle[i]) !== -1) {
4592
            return true;
4593
          }
4594
        }
4595
        return false;
4596
      } else {
4597
        return wysihtml5.lang.array(arr).indexOf(needle) !== -1;
4598
      }
4599
    },
4600
4601
    /**
4602
     * Check whether a given object exists in an array and return index
4603
     * If no elelemt found returns -1
4604
     *
4605
     * @example
4606
     *    wysihtml5.lang.array([1, 2]).indexOf(2);
4607
     *    // => 1
4608
     */
4609
    indexOf: function(needle) {
4610
        if (arr.indexOf) {
4611
          return arr.indexOf(needle);
4612
        } else {
4613
          for (var i=0, length=arr.length; i<length; i++) {
4614
            if (arr[i] === needle) { return i; }
4615
          }
4616
          return -1;
4617
        }
4618
    },
4619
4620
    /**
4621
     * Substract one array from another
4622
     *
4623
     * @example
4624
     *    wysihtml5.lang.array([1, 2, 3, 4]).without([3, 4]);
4625
     *    // => [1, 2]
4626
     */
4627
    without: function(arrayToSubstract) {
4628
      arrayToSubstract = wysihtml5.lang.array(arrayToSubstract);
4629
      var newArr  = [],
4630
          i       = 0,
4631
          length  = arr.length;
4632
      for (; i<length; i++) {
4633
        if (!arrayToSubstract.contains(arr[i])) {
4634
          newArr.push(arr[i]);
4635
        }
4636
      }
4637
      return newArr;
4638
    },
4639
4640
    /**
4641
     * Return a clean native array
4642
     *
4643
     * Following will convert a Live NodeList to a proper Array
4644
     * @example
4645
     *    var childNodes = wysihtml5.lang.array(document.body.childNodes).get();
4646
     */
4647
    get: function() {
4648
      var i        = 0,
4649
          length   = arr.length,
4650
          newArray = [];
4651
      for (; i<length; i++) {
4652
        newArray.push(arr[i]);
4653
      }
4654
      return newArray;
4655
    },
4656
4657
    /**
4658
     * Creates a new array with the results of calling a provided function on every element in this array.
4659
     * optionally this can be provided as second argument
4660
     *
4661
     * @example
4662
     *    var childNodes = wysihtml5.lang.array([1,2,3,4]).map(function (value, index, array) {
4663
            return value * 2;
4664
     *    });
4665
     *    // => [2,4,6,8]
4666
     */
4667
    map: function(callback, thisArg) {
4668
      if (Array.prototype.map) {
4669
        return arr.map(callback, thisArg);
4670
      } else {
4671
        var len = arr.length >>> 0,
4672
            A = new Array(len),
4673
            i = 0;
4674
        for (; i < len; i++) {
4675
           A[i] = callback.call(thisArg, arr[i], i, arr);
4676
        }
4677
        return A;
4678
      }
4679
    },
4680
4681
    /* ReturnS new array without duplicate entries
4682
     *
4683
     * @example
4684
     *    var uniq = wysihtml5.lang.array([1,2,3,2,1,4]).unique();
4685
     *    // => [1,2,3,4]
4686
     */
4687
    unique: function() {
4688
      var vals = [],
4689
          max = arr.length,
4690
          idx = 0;
4691
4692
      while (idx < max) {
4693
        if (!wysihtml5.lang.array(vals).contains(arr[idx])) {
4694
          vals.push(arr[idx]);
4695
        }
4696
        idx++;
4697
      }
4698
      return vals;
4699
    }
4700
4701
  };
4702
};
4703
;wysihtml5.lang.Dispatcher = Base.extend(
4704
  /** @scope wysihtml5.lang.Dialog.prototype */ {
4705
  on: function(eventName, handler) {
4706
    this.events = this.events || {};
4707
    this.events[eventName] = this.events[eventName] || [];
4708
    this.events[eventName].push(handler);
4709
    return this;
4710
  },
4711
4712
  off: function(eventName, handler) {
4713
    this.events = this.events || {};
4714
    var i = 0,
4715
        handlers,
4716
        newHandlers;
4717
    if (eventName) {
4718
      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...
4719
      newHandlers = [];
4720
      for (; i<handlers.length; i++) {
4721
        if (handlers[i] !== handler && handler) {
4722
          newHandlers.push(handlers[i]);
4723
        }
4724
      }
4725
      this.events[eventName] = newHandlers;
4726
    } else {
4727
      // Clean up all events
4728
      this.events = {};
4729
    }
4730
    return this;
4731
  },
4732
4733
  fire: function(eventName, payload) {
4734
    this.events = this.events || {};
4735
    var handlers = this.events[eventName] || [],
4736
        i        = 0;
4737
    for (; i<handlers.length; i++) {
4738
      handlers[i].call(this, payload);
4739
    }
4740
    return this;
4741
  },
4742
4743
  // deprecated, use .on()
4744
  observe: function() {
4745
    return this.on.apply(this, arguments);
4746
  },
4747
4748
  // deprecated, use .off()
4749
  stopObserving: function() {
4750
    return this.off.apply(this, arguments);
4751
  }
4752
});
4753
;wysihtml5.lang.object = function(obj) {
4754
  return {
4755
    /**
4756
     * @example
4757
     *    wysihtml5.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
4758
     *    // => { foo: 1, bar: 2, baz: 3 }
4759
     */
4760
    merge: function(otherObj) {
4761
      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...
4762
        obj[i] = otherObj[i];
4763
      }
4764
      return this;
4765
    },
4766
4767
    get: function() {
4768
      return obj;
4769
    },
4770
4771
    /**
4772
     * @example
4773
     *    wysihtml5.lang.object({ foo: 1 }).clone();
4774
     *    // => { foo: 1 }
4775
     *
4776
     *    v0.4.14 adds options for deep clone : wysihtml5.lang.object({ foo: 1 }).clone(true);
4777
     */
4778
    clone: function(deep) {
4779
      var newObj = {},
4780
          i;
4781
4782
      if (obj === null || !wysihtml5.lang.object(obj).isPlainObject()) {
4783
        return obj;
4784
      }
4785
4786
      for (i in obj) {
4787
        if(obj.hasOwnProperty(i)) {
4788
          if (deep) {
4789
            newObj[i] = wysihtml5.lang.object(obj[i]).clone(deep);
4790
          } else {
4791
            newObj[i] = obj[i];
4792
          }
4793
        }
4794
      }
4795
      return newObj;
4796
    },
4797
4798
    /**
4799
     * @example
4800
     *    wysihtml5.lang.object([]).isArray();
4801
     *    // => true
4802
     */
4803
    isArray: function() {
4804
      return Object.prototype.toString.call(obj) === "[object Array]";
4805
    },
4806
4807
    /**
4808
     * @example
4809
     *    wysihtml5.lang.object(function() {}).isFunction();
4810
     *    // => true
4811
     */
4812
    isFunction: function() {
4813
      return Object.prototype.toString.call(obj) === '[object Function]';
4814
    },
4815
4816
    isPlainObject: function () {
4817
      return Object.prototype.toString.call(obj) === '[object Object]';
4818
    }
4819
  };
4820
};
4821
;(function() {
4822
  var WHITE_SPACE_START = /^\s+/,
4823
      WHITE_SPACE_END   = /\s+$/,
4824
      ENTITY_REG_EXP    = /[&<>\t"]/g,
4825
      ENTITY_MAP = {
4826
        '&': '&amp;',
4827
        '<': '&lt;',
4828
        '>': '&gt;',
4829
        '"': "&quot;",
4830
        '\t':"&nbsp; "
4831
      };
4832
  wysihtml5.lang.string = function(str) {
4833
    str = String(str);
4834
    return {
4835
      /**
4836
       * @example
4837
       *    wysihtml5.lang.string("   foo   ").trim();
4838
       *    // => "foo"
4839
       */
4840
      trim: function() {
4841
        return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
4842
      },
4843
4844
      /**
4845
       * @example
4846
       *    wysihtml5.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
4847
       *    // => "Hello Christopher"
4848
       */
4849
      interpolate: function(vars) {
4850
        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...
4851
          str = this.replace("#{" + i + "}").by(vars[i]);
4852
        }
4853
        return str;
4854
      },
4855
4856
      /**
4857
       * @example
4858
       *    wysihtml5.lang.string("Hello Tom").replace("Tom").with("Hans");
4859
       *    // => "Hello Hans"
4860
       */
4861
      replace: function(search) {
4862
        return {
4863
          by: function(replace) {
4864
            return str.split(search).join(replace);
4865
          }
4866
        };
4867
      },
4868
4869
      /**
4870
       * @example
4871
       *    wysihtml5.lang.string("hello<br>").escapeHTML();
4872
       *    // => "hello&lt;br&gt;"
4873
       */
4874
      escapeHTML: function(linebreaks, convertSpaces) {
4875
        var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
4876
        if (linebreaks) {
4877
          html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
4878
        }
4879
        if (convertSpaces) {
4880
          html = html.replace(/  /gi, "&nbsp; ");
4881
        }
4882
        return html;
4883
      }
4884
    };
4885
  };
4886
})();
4887
;/**
4888
 * Find urls in descendant text nodes of an element and auto-links them
4889
 * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
4890
 *
4891
 * @param {Element} element Container element in which to search for urls
0 ignored issues
show
Documentation introduced by
The parameter element does not exist. Did you maybe forget to remove this comment?
Loading history...
4892
 *
4893
 * @example
4894
 *    <div id="text-container">Please click here: www.google.com</div>
4895
 *    <script>wysihtml5.dom.autoLink(document.getElementById("text-container"));</script>
4896
 */
4897
(function(wysihtml5) {
4898
  var /**
4899
       * Don't auto-link urls that are contained in the following elements:
4900
       */
4901
      IGNORE_URLS_IN        = wysihtml5.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
4902
      /**
4903
       * revision 1:
4904
       *    /(\S+\.{1}[^\s\,\.\!]+)/g
4905
       *
4906
       * revision 2:
4907
       *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
4908
       *
4909
       * put this in the beginning if you don't wan't to match within a word
4910
       *    (^|[\>\(\{\[\s\>])
4911
       */
4912
      URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
4913
      TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
4914
      MAX_DISPLAY_LENGTH    = 100,
4915
      BRACKETS              = { ")": "(", "]": "[", "}": "{" };
4916
4917
  function autoLink(element, ignoreInClasses) {
4918
    if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
4919
      return element;
4920
    }
4921
4922
    if (element === element.ownerDocument.documentElement) {
4923
      element = element.ownerDocument.body;
4924
    }
4925
4926
    return _parseNode(element, ignoreInClasses);
4927
  }
4928
4929
  /**
4930
   * This is basically a rebuild of
4931
   * the rails auto_link_urls text helper
4932
   */
4933
  function _convertUrlsToLinks(str) {
4934
    return str.replace(URL_REG_EXP, function(match, url) {
4935
      var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
4936
          opening     = BRACKETS[punctuation];
4937
      url = url.replace(TRAILING_CHAR_REG_EXP, "");
4938
4939
      if (url.split(opening).length > url.split(punctuation).length) {
4940
        url = url + punctuation;
4941
        punctuation = "";
4942
      }
4943
      var realUrl    = url,
4944
          displayUrl = url;
4945
      if (url.length > MAX_DISPLAY_LENGTH) {
4946
        displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
4947
      }
4948
      // Add http prefix if necessary
4949
      if (realUrl.substr(0, 4) === "www.") {
4950
        realUrl = "http://" + realUrl;
4951
      }
4952
4953
      return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
4954
    });
4955
  }
4956
4957
  /**
4958
   * Creates or (if already cached) returns a temp element
4959
   * for the given document object
4960
   */
4961
  function _getTempElement(context) {
4962
    var tempElement = context._wysihtml5_tempElement;
4963
    if (!tempElement) {
4964
      tempElement = context._wysihtml5_tempElement = context.createElement("div");
4965
    }
4966
    return tempElement;
4967
  }
4968
4969
  /**
4970
   * Replaces the original text nodes with the newly auto-linked dom tree
4971
   */
4972
  function _wrapMatchesInNode(textNode) {
4973
    var parentNode  = textNode.parentNode,
4974
        nodeValue   = wysihtml5.lang.string(textNode.data).escapeHTML(),
4975
        tempElement = _getTempElement(parentNode.ownerDocument);
4976
4977
    // We need to insert an empty/temporary <span /> to fix IE quirks
4978
    // Elsewise IE would strip white space in the beginning
4979
    tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
4980
    tempElement.removeChild(tempElement.firstChild);
4981
4982
    while (tempElement.firstChild) {
4983
      // inserts tempElement.firstChild before textNode
4984
      parentNode.insertBefore(tempElement.firstChild, textNode);
4985
    }
4986
    parentNode.removeChild(textNode);
4987
  }
4988
4989
  function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
4990
    var nodeName;
4991
    while (node.parentNode) {
4992
      node = node.parentNode;
4993
      nodeName = node.nodeName;
4994
      if (node.className && wysihtml5.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
4995
        return true;
4996
      }
4997
      if (IGNORE_URLS_IN.contains(nodeName)) {
4998
        return true;
4999
      } else if (nodeName === "body") {
5000
        return false;
5001
      }
5002
    }
5003
    return false;
5004
  }
5005
5006
  function _parseNode(element, ignoreInClasses) {
5007
    if (IGNORE_URLS_IN.contains(element.nodeName)) {
5008
      return;
5009
    }
5010
5011
    if (element.className && wysihtml5.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
5012
      return;
5013
    }
5014
5015
    if (element.nodeType === wysihtml5.TEXT_NODE && element.data.match(URL_REG_EXP)) {
5016
      _wrapMatchesInNode(element);
5017
      return;
5018
    }
5019
5020
    var childNodes        = wysihtml5.lang.array(element.childNodes).get(),
5021
        childNodesLength  = childNodes.length,
5022
        i                 = 0;
5023
5024
    for (; i<childNodesLength; i++) {
5025
      _parseNode(childNodes[i], ignoreInClasses);
5026
    }
5027
5028
    return element;
5029
  }
5030
5031
  wysihtml5.dom.autoLink = autoLink;
5032
5033
  // Reveal url reg exp to the outside
5034
  wysihtml5.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
5035
})(wysihtml5);
5036
;(function(wysihtml5) {
5037
  var api = wysihtml5.dom;
5038
5039
  api.addClass = function(element, className) {
5040
    var classList = element.classList;
5041
    if (classList) {
5042
      return classList.add(className);
5043
    }
5044
    if (api.hasClass(element, className)) {
5045
      return;
5046
    }
5047
    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...
5048
  };
5049
5050
  api.removeClass = function(element, className) {
5051
    var classList = element.classList;
5052
    if (classList) {
5053
      return classList.remove(className);
5054
    }
5055
5056
    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...
5057
  };
5058
5059
  api.hasClass = function(element, className) {
5060
    var classList = element.classList;
5061
    if (classList) {
5062
      return classList.contains(className);
5063
    }
5064
5065
    var elementClassName = element.className;
5066
    return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
5067
  };
5068
})(wysihtml5);
5069
;wysihtml5.dom.contains = (function() {
5070
  var documentElement = document.documentElement;
5071
  if (documentElement.contains) {
5072
    return function(container, element) {
5073
      if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5074
        element = element.parentNode;
5075
      }
5076
      return container !== element && container.contains(element);
5077
    };
5078
  } 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...
5079
    return function(container, element) {
5080
      // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
5081
      return !!(container.compareDocumentPosition(element) & 16);
5082
    };
5083
  }
5084
})();
5085
;/**
5086
 * Converts an HTML fragment/element into a unordered/ordered list
5087
 *
5088
 * @param {Element} element The element which should be turned into a list
0 ignored issues
show
Documentation introduced by
The parameter element does not exist. Did you maybe forget to remove this comment?
Loading history...
5089
 * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
0 ignored issues
show
Documentation introduced by
The parameter listType does not exist. Did you maybe forget to remove this comment?
Loading history...
5090
 * @return {Element} The created list
5091
 *
5092
 * @example
5093
 *    <!-- Assume the following dom: -->
5094
 *    <span id="pseudo-list">
5095
 *      eminem<br>
5096
 *      dr. dre
5097
 *      <div>50 Cent</div>
5098
 *    </span>
5099
 *
5100
 *    <script>
5101
 *      wysihtml5.dom.convertToList(document.getElementById("pseudo-list"), "ul");
5102
 *    </script>
5103
 *
5104
 *    <!-- Will result in: -->
5105
 *    <ul>
5106
 *      <li>eminem</li>
5107
 *      <li>dr. dre</li>
5108
 *      <li>50 Cent</li>
5109
 *    </ul>
5110
 */
5111
wysihtml5.dom.convertToList = (function() {
5112
  function _createListItem(doc, list) {
5113
    var listItem = doc.createElement("li");
5114
    list.appendChild(listItem);
5115
    return listItem;
5116
  }
5117
5118
  function _createList(doc, type) {
5119
    return doc.createElement(type);
5120
  }
5121
5122
  function convertToList(element, listType, uneditableClass) {
5123
    if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
5124
      // Already a list
5125
      return element;
5126
    }
5127
5128
    var doc               = element.ownerDocument,
5129
        list              = _createList(doc, listType),
5130
        lineBreaks        = element.querySelectorAll("br"),
5131
        lineBreaksLength  = lineBreaks.length,
5132
        childNodes,
5133
        childNodesLength,
5134
        childNode,
5135
        lineBreak,
5136
        parentNode,
5137
        isBlockElement,
5138
        isLineBreak,
5139
        currentListItem,
5140
        i;
5141
5142
    // First find <br> at the end of inline elements and move them behind them
5143
    for (i=0; i<lineBreaksLength; i++) {
5144
      lineBreak = lineBreaks[i];
5145
      while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
5146
        if (wysihtml5.dom.getStyle("display").from(parentNode) === "block") {
5147
          parentNode.removeChild(lineBreak);
5148
          break;
5149
        }
5150
        wysihtml5.dom.insert(lineBreak).after(lineBreak.parentNode);
5151
      }
5152
    }
5153
5154
    childNodes        = wysihtml5.lang.array(element.childNodes).get();
5155
    childNodesLength  = childNodes.length;
5156
5157
    for (i=0; i<childNodesLength; i++) {
5158
      currentListItem   = currentListItem || _createListItem(doc, list);
5159
      childNode         = childNodes[i];
5160
      isBlockElement    = wysihtml5.dom.getStyle("display").from(childNode) === "block";
5161
      isLineBreak       = childNode.nodeName === "BR";
5162
5163
      // consider uneditable as an inline element
5164
      if (isBlockElement && (!uneditableClass || !wysihtml5.dom.hasClass(childNode, uneditableClass))) {
5165
        // Append blockElement to current <li> if empty, otherwise create a new one
5166
        currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
5167
        currentListItem.appendChild(childNode);
5168
        currentListItem = null;
5169
        continue;
5170
      }
5171
5172
      if (isLineBreak) {
5173
        // Only create a new list item in the next iteration when the current one has already content
5174
        currentListItem = currentListItem.firstChild ? null : currentListItem;
5175
        continue;
5176
      }
5177
5178
      currentListItem.appendChild(childNode);
5179
    }
5180
5181
    if (childNodes.length === 0) {
5182
      _createListItem(doc, list);
5183
    }
5184
5185
    element.parentNode.replaceChild(list, element);
5186
    return list;
5187
  }
5188
5189
  return convertToList;
5190
})();
5191
;/**
5192
 * Copy a set of attributes from one element to another
5193
 *
5194
 * @param {Array} attributesToCopy List of attributes 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 attributes from., this again returns an object which provides a method named "to" which can be invoked
5197
 *    with the element where to copy the attributes 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.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
5204
 *
5205
 */
5206
wysihtml5.dom.copyAttributes = function(attributesToCopy) {
5207
  return {
5208
    from: function(elementToCopyFrom) {
5209
      return {
5210
        to: function(elementToCopyTo) {
5211
          var attribute,
5212
              i         = 0,
5213
              length    = attributesToCopy.length;
5214
          for (; i<length; i++) {
5215
            attribute = attributesToCopy[i];
5216
            if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
5217
              elementToCopyTo[attribute] = elementToCopyFrom[attribute];
5218
            }
5219
          }
5220
          return { andTo: arguments.callee };
5221
        }
5222
      };
5223
    }
5224
  };
5225
};
5226
;/**
5227
 * Copy a set of styles from one element to another
5228
 * Please note that this only works properly across browsers when the element from which to copy the styles
5229
 * is in the dom
5230
 *
5231
 * Interesting article on how to copy styles
5232
 *
5233
 * @param {Array} stylesToCopy List of styles which should be copied
0 ignored issues
show
Documentation introduced by
The parameter stylesToCopy does not exist. Did you maybe forget to remove this comment?
Loading history...
5234
 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
5235
 *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked
5236
 *    with the element where to copy the styles to (see example)
5237
 *
5238
 * @example
5239
 *    var textarea    = document.querySelector("textarea"),
5240
 *        div         = document.querySelector("div[contenteditable=true]"),
5241
 *        anotherDiv  = document.querySelector("div.preview");
5242
 *    wysihtml5.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
5243
 *
5244
 */
5245
(function(dom) {
5246
5247
  /**
5248
   * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
5249
   * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
5250
   * its computed css width will be 198px
5251
   *
5252
   * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
5253
   */
5254
  var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
5255
5256
  var shouldIgnoreBoxSizingBorderBox = function(element) {
5257
    if (hasBoxSizingBorderBox(element)) {
5258
       return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
5259
    }
5260
    return false;
5261
  };
5262
5263
  var hasBoxSizingBorderBox = function(element) {
5264
    var i       = 0,
5265
        length  = BOX_SIZING_PROPERTIES.length;
5266
    for (; i<length; i++) {
5267
      if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
5268
        return BOX_SIZING_PROPERTIES[i];
5269
      }
5270
    }
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...
5271
  };
5272
5273
  dom.copyStyles = function(stylesToCopy) {
5274
    return {
5275
      from: function(element) {
5276
        if (shouldIgnoreBoxSizingBorderBox(element)) {
5277
          stylesToCopy = wysihtml5.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
5278
        }
5279
5280
        var cssText = "",
5281
            length  = stylesToCopy.length,
5282
            i       = 0,
5283
            property;
5284
        for (; i<length; i++) {
5285
          property = stylesToCopy[i];
5286
          cssText += property + ":" + dom.getStyle(property).from(element) + ";";
5287
        }
5288
5289
        return {
5290
          to: function(element) {
5291
            dom.setStyles(cssText).on(element);
5292
            return { andTo: arguments.callee };
5293
          }
5294
        };
5295
      }
5296
    };
5297
  };
5298
})(wysihtml5.dom);
5299
;/**
5300
 * Event Delegation
5301
 *
5302
 * @example
5303
 *    wysihtml5.dom.delegate(document.body, "a", "click", function() {
5304
 *      // foo
5305
 *    });
5306
 */
5307
(function(wysihtml5) {
5308
5309
  wysihtml5.dom.delegate = function(container, selector, eventName, handler) {
5310
    return wysihtml5.dom.observe(container, eventName, function(event) {
5311
      var target    = event.target,
5312
          match     = wysihtml5.lang.array(container.querySelectorAll(selector));
5313
5314
      while (target && target !== container) {
5315
        if (match.contains(target)) {
5316
          handler.call(target, event);
5317
          break;
5318
        }
5319
        target = target.parentNode;
5320
      }
5321
    });
5322
  };
5323
5324
})(wysihtml5);
5325
;// TODO: Refactor dom tree traversing here
5326
(function(wysihtml5) {
5327
  wysihtml5.dom.domNode = function(node) {
5328
    var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
5329
5330
    var _isBlankText = function(node) {
5331
      return node.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/g).test(node.data);
5332
    };
5333
5334
    return {
5335
5336
      // var node = wysihtml5.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
5337
      prev: function(options) {
5338
        var prevNode = node.previousSibling,
5339
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5340
        
5341
        if (!prevNode) {
5342
          return null;
5343
        }
5344
5345
        if (
5346
          (!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
5347
          (options && options.ignoreBlankTexts && _isBlankText(prevNode)) // Blank text nodes bypassed if set
5348
        ) {
5349
          return wysihtml5.dom.domNode(prevNode).prev(options);
5350
        }
5351
        
5352
        return prevNode;
5353
      },
5354
5355
      // var node = wysihtml5.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
5356
      next: function(options) {
5357
        var nextNode = node.nextSibling,
5358
            types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
5359
        
5360
        if (!nextNode) {
5361
          return null;
5362
        }
5363
5364
        if (
5365
          (!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
5366
          (options && options.ignoreBlankTexts && _isBlankText(nextNode)) // blank text nodes bypassed if set
5367
        ) {
5368
          return wysihtml5.dom.domNode(nextNode).next(options);
5369
        }
5370
        
5371
        return nextNode;
5372
      }
5373
5374
5375
5376
    };
5377
  };
5378
})(wysihtml5);;/**
5379
 * Returns the given html wrapped in a div element
5380
 *
5381
 * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
5382
 * when inserted via innerHTML
5383
 *
5384
 * @param {String} html The html which should be wrapped in a dom element
0 ignored issues
show
Documentation introduced by
The parameter html does not exist. Did you maybe forget to remove this comment?
Loading history...
5385
 * @param {Obejct} [context] Document object of the context the html belongs to
0 ignored issues
show
Documentation introduced by
The parameter context does not exist. Did you maybe forget to remove this comment?
Loading history...
5386
 *
5387
 * @example
5388
 *    wysihtml5.dom.getAsDom("<article>foo</article>");
5389
 */
5390
wysihtml5.dom.getAsDom = (function() {
5391
5392
  var _innerHTMLShiv = function(html, context) {
5393
    var tempElement = context.createElement("div");
5394
    tempElement.style.display = "none";
5395
    context.body.appendChild(tempElement);
5396
    // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
5397
    try { tempElement.innerHTML = html; } catch(e) {}
5398
    context.body.removeChild(tempElement);
5399
    return tempElement;
5400
  };
5401
5402
  /**
5403
   * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
5404
   */
5405
  var _ensureHTML5Compatibility = function(context) {
5406
    if (context._wysihtml5_supportsHTML5Tags) {
5407
      return;
5408
    }
5409
    for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
5410
      context.createElement(HTML5_ELEMENTS[i]);
5411
    }
5412
    context._wysihtml5_supportsHTML5Tags = true;
5413
  };
5414
5415
5416
  /**
5417
   * List of html5 tags
5418
   * taken from http://simon.html5.org/html5-elements
5419
   */
5420
  var HTML5_ELEMENTS = [
5421
    "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
5422
    "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
5423
    "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
5424
  ];
5425
5426
  return function(html, context) {
5427
    context = context || document;
5428
    var tempElement;
5429
    if (typeof(html) === "object" && html.nodeType) {
5430
      tempElement = context.createElement("div");
5431
      tempElement.appendChild(html);
5432
    } else if (wysihtml5.browser.supportsHTML5Tags(context)) {
5433
      tempElement = context.createElement("div");
5434
      tempElement.innerHTML = html;
5435
    } else {
5436
      _ensureHTML5Compatibility(context);
5437
      tempElement = _innerHTMLShiv(html, context);
5438
    }
5439
    return tempElement;
5440
  };
5441
})();
5442
;/**
5443
 * Walks the dom tree from the given node up until it finds a match
5444
 * Designed for optimal performance.
5445
 *
5446
 * @param {Element} node The from which to check the parent nodes
0 ignored issues
show
Documentation introduced by
The parameter node does not exist. Did you maybe forget to remove this comment?
Loading history...
5447
 * @param {Object} matchingSet Object to match against (possible properties: nodeName, className, classRegExp)
0 ignored issues
show
Documentation introduced by
The parameter matchingSet does not exist. Did you maybe forget to remove this comment?
Loading history...
5448
 * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
0 ignored issues
show
Documentation introduced by
The parameter levels does not exist. Did you maybe forget to remove this comment?
Loading history...
5449
 * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
5450
 * @example
5451
 *    var listElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: ["MENU", "UL", "OL"] });
5452
 *    // ... or ...
5453
 *    var unorderedListElement = wysihtml5.dom.getParentElement(document.querySelector("li"), { nodeName: "UL" });
5454
 *    // ... or ...
5455
 *    var coloredElement = wysihtml5.dom.getParentElement(myTextNode, { nodeName: "SPAN", className: "wysiwyg-color-red", classRegExp: /wysiwyg-color-[a-z]/g });
5456
 */
5457
wysihtml5.dom.getParentElement = (function() {
5458
5459
  function _isSameNodeName(nodeName, desiredNodeNames) {
5460
    if (!desiredNodeNames || !desiredNodeNames.length) {
5461
      return true;
5462
    }
5463
5464
    if (typeof(desiredNodeNames) === "string") {
5465
      return nodeName === desiredNodeNames;
5466
    } else {
5467
      return wysihtml5.lang.array(desiredNodeNames).contains(nodeName);
5468
    }
5469
  }
5470
5471
  function _isElement(node) {
5472
    return node.nodeType === wysihtml5.ELEMENT_NODE;
5473
  }
5474
5475
  function _hasClassName(element, className, classRegExp) {
5476
    var classNames = (element.className || "").match(classRegExp) || [];
5477
    if (!className) {
5478
      return !!classNames.length;
5479
    }
5480
    return classNames[classNames.length - 1] === className;
5481
  }
5482
5483
  function _hasStyle(element, cssStyle, styleRegExp) {
5484
    var styles = (element.getAttribute('style') || "").match(styleRegExp) || [];
5485
    if (!cssStyle) {
5486
      return !!styles.length;
5487
    }
5488
    return styles[styles.length - 1] === cssStyle;
5489
  }
5490
5491
  return function(node, matchingSet, levels, container) {
5492
    var findByStyle = (matchingSet.cssStyle || matchingSet.styleRegExp),
5493
        findByClass = (matchingSet.className || matchingSet.classRegExp);
5494
5495
    levels = levels || 50; // Go max 50 nodes upwards from current node
5496
5497
    while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
5498
      if (_isElement(node) && _isSameNodeName(node.nodeName, matchingSet.nodeName) &&
5499
          (!findByStyle || _hasStyle(node, matchingSet.cssStyle, matchingSet.styleRegExp)) &&
5500
          (!findByClass || _hasClassName(node, matchingSet.className, matchingSet.classRegExp))
5501
      ) {
5502
        return node;
5503
      }
5504
      node = node.parentNode;
5505
    }
5506
    return null;
5507
  };
5508
})();
5509
;/**
5510
 * Get element's style for a specific css property
5511
 *
5512
 * @param {Element} element The element on which to retrieve the style
0 ignored issues
show
Documentation introduced by
The parameter element does not exist. Did you maybe forget to remove this comment?
Loading history...
5513
 * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
0 ignored issues
show
Documentation introduced by
The parameter property does not exist. Did you maybe forget to remove this comment?
Loading history...
5514
 *
5515
 * @example
5516
 *    wysihtml5.dom.getStyle("display").from(document.body);
5517
 *    // => "block"
5518
 */
5519
wysihtml5.dom.getStyle = (function() {
5520
  var stylePropertyMapping = {
5521
        "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
5522
      },
5523
      REG_EXP_CAMELIZE = /\-[a-z]/g;
5524
5525
  function camelize(str) {
5526
    return str.replace(REG_EXP_CAMELIZE, function(match) {
5527
      return match.charAt(1).toUpperCase();
5528
    });
5529
  }
5530
5531
  return function(property) {
5532
    return {
5533
      from: function(element) {
5534
        if (element.nodeType !== wysihtml5.ELEMENT_NODE) {
5535
          return;
5536
        }
5537
5538
        var doc               = element.ownerDocument,
5539
            camelizedProperty = stylePropertyMapping[property] || camelize(property),
5540
            style             = element.style,
5541
            currentStyle      = element.currentStyle,
5542
            styleValue        = style[camelizedProperty];
5543
        if (styleValue) {
5544
          return styleValue;
5545
        }
5546
5547
        // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
5548
        // window.getComputedStyle, since it returns css property values in their original unit:
5549
        // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
5550
        // gives you the original "50%".
5551
        // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
5552
        if (currentStyle) {
5553
          try {
5554
            return currentStyle[camelizedProperty];
5555
          } catch(e) {
5556
            //ie will occasionally fail for unknown reasons. swallowing exception
5557
          }
5558
        }
5559
5560
        var win                 = doc.defaultView || doc.parentWindow,
5561
            needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
5562
            originalOverflow,
5563
            returnValue;
5564
5565
        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...
5566
          // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
5567
          // therfore we remove and restore the scrollbar and calculate the value in between
5568
          if (needsOverflowReset) {
5569
            originalOverflow = style.overflow;
5570
            style.overflow = "hidden";
5571
          }
5572
          returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
5573
          if (needsOverflowReset) {
5574
            style.overflow = originalOverflow || "";
5575
          }
5576
          return returnValue;
5577
        }
5578
      }
5579
    };
5580
  };
5581
})();
5582
;wysihtml5.dom.getTextNodes = function(node, ingoreEmpty){
5583
  var all = [];
5584
  for (node=node.firstChild;node;node=node.nextSibling){
5585
    if (node.nodeType == 3) {
5586
      if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
5587
        all.push(node);
5588
      }
5589
    } else {
5590
      all = all.concat(wysihtml5.dom.getTextNodes(node, ingoreEmpty));
5591
    }
5592
  }
5593
  return all;
5594
};;/**
5595
 * High performant way to check whether an element with a specific tag name is in the given document
5596
 * Optimized for being heavily executed
5597
 * Unleashes the power of live node lists
5598
 *
5599
 * @param {Object} doc The document object of the context where to check
0 ignored issues
show
Documentation introduced by
The parameter doc does not exist. Did you maybe forget to remove this comment?
Loading history...
5600
 * @param {String} tagName Upper cased tag name
0 ignored issues
show
Documentation introduced by
The parameter tagName does not exist. Did you maybe forget to remove this comment?
Loading history...
5601
 * @example
5602
 *    wysihtml5.dom.hasElementWithTagName(document, "IMG");
5603
 */
5604
wysihtml5.dom.hasElementWithTagName = (function() {
5605
  var LIVE_CACHE          = {},
5606
      DOCUMENT_IDENTIFIER = 1;
5607
5608
  function _getDocumentIdentifier(doc) {
5609
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5610
  }
5611
5612
  return function(doc, tagName) {
5613
    var key         = _getDocumentIdentifier(doc) + ":" + tagName,
5614
        cacheEntry  = LIVE_CACHE[key];
5615
    if (!cacheEntry) {
5616
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
5617
    }
5618
5619
    return cacheEntry.length > 0;
5620
  };
5621
})();
5622
;/**
5623
 * High performant way to check whether an element with a specific class name is in the given document
5624
 * Optimized for being heavily executed
5625
 * Unleashes the power of live node lists
5626
 *
5627
 * @param {Object} doc The document object of the context where to check
0 ignored issues
show
Documentation introduced by
The parameter doc does not exist. Did you maybe forget to remove this comment?
Loading history...
5628
 * @param {String} tagName Upper cased tag name
0 ignored issues
show
Documentation introduced by
The parameter tagName does not exist. Did you maybe forget to remove this comment?
Loading history...
5629
 * @example
5630
 *    wysihtml5.dom.hasElementWithClassName(document, "foobar");
5631
 */
5632
(function(wysihtml5) {
5633
  var LIVE_CACHE          = {},
5634
      DOCUMENT_IDENTIFIER = 1;
5635
5636
  function _getDocumentIdentifier(doc) {
5637
    return doc._wysihtml5_identifier || (doc._wysihtml5_identifier = DOCUMENT_IDENTIFIER++);
5638
  }
5639
5640
  wysihtml5.dom.hasElementWithClassName = function(doc, className) {
5641
    // getElementsByClassName is not supported by IE<9
5642
    // but is sometimes mocked via library code (which then doesn't return live node lists)
5643
    if (!wysihtml5.browser.supportsNativeGetElementsByClassName()) {
5644
      return !!doc.querySelector("." + className);
5645
    }
5646
5647
    var key         = _getDocumentIdentifier(doc) + ":" + className,
5648
        cacheEntry  = LIVE_CACHE[key];
5649
    if (!cacheEntry) {
5650
      cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
5651
    }
5652
5653
    return cacheEntry.length > 0;
5654
  };
5655
})(wysihtml5);
5656
;wysihtml5.dom.insert = function(elementToInsert) {
5657
  return {
5658
    after: function(element) {
5659
      element.parentNode.insertBefore(elementToInsert, element.nextSibling);
5660
    },
5661
5662
    before: function(element) {
5663
      element.parentNode.insertBefore(elementToInsert, element);
5664
    },
5665
5666
    into: function(element) {
5667
      element.appendChild(elementToInsert);
5668
    }
5669
  };
5670
};
5671
;wysihtml5.dom.insertCSS = function(rules) {
5672
  rules = rules.join("\n");
5673
5674
  return {
5675
    into: function(doc) {
5676
      var styleElement = doc.createElement("style");
5677
      styleElement.type = "text/css";
5678
5679
      if (styleElement.styleSheet) {
5680
        styleElement.styleSheet.cssText = rules;
5681
      } else {
5682
        styleElement.appendChild(doc.createTextNode(rules));
5683
      }
5684
5685
      var link = doc.querySelector("head link");
5686
      if (link) {
5687
        link.parentNode.insertBefore(styleElement, link);
5688
        return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
5689
      } else {
5690
        var head = doc.querySelector("head");
5691
        if (head) {
5692
          head.appendChild(styleElement);
5693
        }
5694
      }
5695
    }
5696
  };
5697
};
5698
;// TODO: Refactor dom tree traversing here
5699
(function(wysihtml5) {
5700
  wysihtml5.dom.lineBreaks = function(node) {
5701
5702
    function _isLineBreak(n) {
5703
      return n.nodeName === "BR";
5704
    }
5705
5706
    /**
5707
     * Checks whether the elment causes a visual line break
5708
     * (<br> or block elements)
5709
     */
5710
    function _isLineBreakOrBlockElement(element) {
5711
      if (_isLineBreak(element)) {
5712
        return true;
5713
      }
5714
5715
      if (wysihtml5.dom.getStyle("display").from(element) === "block") {
5716
        return true;
5717
      }
5718
5719
      return false;
5720
    }
5721
5722
    return {
5723
5724
      /* wysihtml5.dom.lineBreaks(element).add();
5725
       *
5726
       * Adds line breaks before and after the given node if the previous and next siblings
5727
       * aren't already causing a visual line break (block element or <br>)
5728
       */
5729
      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...
5730
        var doc             = node.ownerDocument,
5731
          nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5732
          previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5733
5734
        if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
5735
          wysihtml5.dom.insert(doc.createElement("br")).after(node);
5736
        }
5737
        if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
5738
          wysihtml5.dom.insert(doc.createElement("br")).before(node);
5739
        }
5740
      },
5741
5742
      /* wysihtml5.dom.lineBreaks(element).remove();
5743
       *
5744
       * Removes line breaks before and after the given node
5745
       */
5746
      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...
5747
        var nextSibling     = wysihtml5.dom.domNode(node).next({ignoreBlankTexts: true}),
5748
            previousSibling = wysihtml5.dom.domNode(node).prev({ignoreBlankTexts: true});
5749
5750
        if (nextSibling && _isLineBreak(nextSibling)) {
5751
          nextSibling.parentNode.removeChild(nextSibling);
5752
        }
5753
        if (previousSibling && _isLineBreak(previousSibling)) {
5754
          previousSibling.parentNode.removeChild(previousSibling);
5755
        }
5756
      }
5757
    };
5758
  };
5759
})(wysihtml5);;/**
5760
 * Method to set dom events
5761
 *
5762
 * @example
5763
 *    wysihtml5.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
5764
 */
5765
wysihtml5.dom.observe = function(element, eventNames, handler) {
5766
  eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
5767
5768
  var handlerWrapper,
5769
      eventName,
5770
      i       = 0,
5771
      length  = eventNames.length;
5772
5773
  for (; i<length; i++) {
5774
    eventName = eventNames[i];
5775
    if (element.addEventListener) {
5776
      element.addEventListener(eventName, handler, false);
5777
    } else {
5778
      handlerWrapper = function(event) {
5779
        if (!("target" in event)) {
5780
          event.target = event.srcElement;
5781
        }
5782
        event.preventDefault = event.preventDefault || function() {
5783
          this.returnValue = false;
5784
        };
5785
        event.stopPropagation = event.stopPropagation || function() {
5786
          this.cancelBubble = true;
5787
        };
5788
        handler.call(element, event);
5789
      };
5790
      element.attachEvent("on" + eventName, handlerWrapper);
5791
    }
5792
  }
5793
5794
  return {
5795
    stop: function() {
5796
      var eventName,
5797
          i       = 0,
5798
          length  = eventNames.length;
5799
      for (; i<length; i++) {
5800
        eventName = eventNames[i];
5801
        if (element.removeEventListener) {
5802
          element.removeEventListener(eventName, handler, false);
5803
        } else {
5804
          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...
5805
        }
5806
      }
5807
    }
5808
  };
5809
};
5810
;/**
5811
 * HTML Sanitizer
5812
 * Rewrites the HTML based on given rules
5813
 *
5814
 * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
5815
 * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
5816
 *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
5817
 *    desired substitution.
5818
 * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
5819
 *
5820
 * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
5821
 *
5822
 * @example
5823
 *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
5824
 *    wysihtml5.dom.parse(userHTML, {
5825
 *      tags {
5826
 *        p:      "div",      // Rename p tags to div tags
5827
 *        font:   "span"      // Rename font tags to span tags
5828
 *        div:    true,       // Keep them, also possible (same result when passing: "div" or true)
5829
 *        script: undefined   // Remove script elements
5830
 *      }
5831
 *    });
5832
 *    // => <div><div><span>foo bar</span></div></div>
5833
 *
5834
 *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
5835
 *    wysihtml5.dom.parse(userHTML);
5836
 *    // => '<span><span><span><span>I'm a table!</span></span></span></span>'
5837
 *
5838
 *    var userHTML = '<div>foobar<br>foobar</div>';
5839
 *    wysihtml5.dom.parse(userHTML, {
5840
 *      tags: {
5841
 *        div: undefined,
5842
 *        br:  true
5843
 *      }
5844
 *    });
5845
 *    // => ''
5846
 *
5847
 *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
5848
 *    wysihtml5.dom.parse(userHTML, {
5849
 *      classes: {
5850
 *        red:    1,
5851
 *        green:  1
5852
 *      },
5853
 *      tags: {
5854
 *        div: {
5855
 *          rename_tag:     "p"
5856
 *        }
5857
 *      }
5858
 *    });
5859
 *    // => '<p class="red">foo</p><p>bar</p>'
5860
 */
5861
5862
wysihtml5.dom.parse = function(elementOrHtml_current, config_current) {
5863
  /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
5864
   * Refactor whole code as this method while workind is kind of awkward too */
5865
5866
  /**
5867
   * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
5868
   * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
5869
   * node isn't closed
5870
   *
5871
   * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
5872
   */
5873
  var NODE_TYPE_MAPPING = {
5874
        "1": _handleElement,
5875
        "3": _handleText,
5876
        "8": _handleComment
5877
      },
5878
      // Rename unknown tags to this
5879
      DEFAULT_NODE_NAME   = "span",
5880
      WHITE_SPACE_REG_EXP = /\s+/,
5881
      defaultRules        = { tags: {}, classes: {} },
5882
      currentRules        = {};
5883
5884
  /**
5885
   * Iterates over all childs of the element, recreates them, appends them into a document fragment
5886
   * which later replaces the entire body content
5887
   */
5888
   function parse(elementOrHtml, config) {
5889
    wysihtml5.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
5890
5891
    var context       = config.context || elementOrHtml.ownerDocument || document,
5892
        fragment      = context.createDocumentFragment(),
5893
        isString      = typeof(elementOrHtml) === "string",
5894
        clearInternals = false,
5895
        element,
5896
        newNode,
5897
        firstChild;
5898
5899
    if (config.clearInternals === true) {
5900
      clearInternals = true;
5901
    }
5902
5903
    if (isString) {
5904
      element = wysihtml5.dom.getAsDom(elementOrHtml, context);
5905
    } else {
5906
      element = elementOrHtml;
5907
    }
5908
5909
    if (currentRules.selectors) {
5910
      _applySelectorRules(element, currentRules.selectors);
5911
    }
5912
5913
    while (element.firstChild) {
5914
      firstChild = element.firstChild;
5915
      newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
5916
      if (newNode) {
5917
        fragment.appendChild(newNode);
5918
      }
5919
      if (firstChild !== newNode) {
5920
        element.removeChild(firstChild);
5921
      }
5922
    }
5923
5924
    if (config.unjoinNbsps) {
5925
      // replace joined non-breakable spaces with unjoined
5926
      var txtnodes = wysihtml5.dom.getTextNodes(fragment);
5927
      for (var n = txtnodes.length; n--;) {
5928
        txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
5929
      }
5930
    }
5931
5932
    // Clear element contents
5933
    element.innerHTML = "";
5934
5935
    // Insert new DOM tree
5936
    element.appendChild(fragment);
5937
5938
    return isString ? wysihtml5.quirks.getCorrectInnerHTML(element) : element;
5939
  }
5940
5941
  function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
5942
    var oldNodeType     = oldNode.nodeType,
5943
        oldChilds       = oldNode.childNodes,
5944
        oldChildsLength = oldChilds.length,
5945
        method          = NODE_TYPE_MAPPING[oldNodeType],
5946
        i               = 0,
5947
        fragment,
5948
        newNode,
5949
        newChild;
5950
5951
    // Passes directly elemets with uneditable class
5952
    if (uneditableClass && oldNodeType === 1 && wysihtml5.dom.hasClass(oldNode, uneditableClass)) {
5953
        return oldNode;
5954
    }
5955
5956
    newNode = method && method(oldNode, clearInternals);
5957
5958
    // Remove or unwrap node in case of return value null or false
5959
    if (!newNode) {
5960
        if (newNode === false) {
5961
            // false defines that tag should be removed but contents should remain (unwrap)
5962
            fragment = oldNode.ownerDocument.createDocumentFragment();
5963
5964
            for (i = oldChildsLength; i--;) {
5965
              if (oldChilds[i]) {
5966
                newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
5967
                if (newChild) {
5968
                  if (oldChilds[i] === newChild) {
5969
                    i--;
5970
                  }
5971
                  fragment.insertBefore(newChild, fragment.firstChild);
5972
                }
5973
              }
5974
            }
5975
5976
            if (wysihtml5.dom.getStyle("display").from(oldNode) === "block") {
5977
              fragment.appendChild(oldNode.ownerDocument.createElement("br"));
5978
            }
5979
5980
            // TODO: try to minimize surplus spaces
5981
            if (wysihtml5.lang.array([
5982
                "div", "pre", "p",
5983
                "table", "td", "th",
5984
                "ul", "ol", "li",
5985
                "dd", "dl",
5986
                "footer", "header", "section",
5987
                "h1", "h2", "h3", "h4", "h5", "h6"
5988
            ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
5989
                // add space at first when unwraping non-textflow elements
5990
                if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
5991
                  fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
5992
                }
5993
            }
5994
5995
            if (fragment.normalize) {
5996
              fragment.normalize();
5997
            }
5998
            return fragment;
5999
        } else {
6000
          // Remove
6001
          return null;
6002
        }
6003
    }
6004
6005
    // Converts all childnodes
6006
    for (i=0; i<oldChildsLength; i++) {
6007
      if (oldChilds[i]) {
6008
        newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
6009
        if (newChild) {
6010
          if (oldChilds[i] === newChild) {
6011
            i--;
6012
          }
6013
          newNode.appendChild(newChild);
6014
        }
6015
      }
6016
    }
6017
6018
    // Cleanup senseless <span> elements
6019
    if (cleanUp &&
6020
        newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
6021
        (!newNode.childNodes.length ||
6022
         ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml5-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
6023
         !newNode.attributes.length)
6024
        ) {
6025
      fragment = newNode.ownerDocument.createDocumentFragment();
6026
      while (newNode.firstChild) {
6027
        fragment.appendChild(newNode.firstChild);
6028
      }
6029
      if (fragment.normalize) {
6030
        fragment.normalize();
6031
      }
6032
      return fragment;
6033
    }
6034
6035
    if (newNode.normalize) {
6036
      newNode.normalize();
6037
    }
6038
    return newNode;
6039
  }
6040
6041
  function _applySelectorRules (element, selectorRules) {
6042
    var sel, method, els;
6043
6044
    for (sel in selectorRules) {
6045
      if (selectorRules.hasOwnProperty(sel)) {
6046
        if (wysihtml5.lang.object(selectorRules[sel]).isFunction()) {
6047
          method = selectorRules[sel];
6048
        } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
6049
          method = elementHandlingMethods[selectorRules[sel]];
6050
        }
6051
        els = element.querySelectorAll(sel);
6052
        for (var i = els.length; i--;) {
6053
          method(els[i]);
6054
        }
6055
      }
6056
    }
6057
  }
6058
6059
  function _handleElement(oldNode, clearInternals) {
6060
    var rule,
6061
        newNode,
6062
        tagRules    = currentRules.tags,
6063
        nodeName    = oldNode.nodeName.toLowerCase(),
6064
        scopeName   = oldNode.scopeName,
6065
        renameTag;
6066
6067
    /**
6068
     * We already parsed that element
6069
     * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
6070
     */
6071
    if (oldNode._wysihtml5) {
6072
      return null;
6073
    }
6074
    oldNode._wysihtml5 = 1;
6075
6076
    if (oldNode.className === "wysihtml5-temp") {
6077
      return null;
6078
    }
6079
6080
    /**
6081
     * IE is the only browser who doesn't include the namespace in the
6082
     * nodeName, that's why we have to prepend it by ourselves
6083
     * scopeName is a proprietary IE feature
6084
     * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
6085
     */
6086
    if (scopeName && scopeName != "HTML") {
6087
      nodeName = scopeName + ":" + nodeName;
6088
    }
6089
    /**
6090
     * Repair node
6091
     * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
6092
     * 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
6093
     */
6094
    if ("outerHTML" in oldNode) {
6095
      if (!wysihtml5.browser.autoClosesUnclosedTags() &&
6096
          oldNode.nodeName === "P" &&
6097
          oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
6098
        nodeName = "div";
6099
      }
6100
    }
6101
6102
    if (nodeName in tagRules) {
6103
      rule = tagRules[nodeName];
6104
      if (!rule || rule.remove) {
6105
        return null;
6106
      } else if (rule.unwrap) {
6107
        return false;
6108
      }
6109
      rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
6110
    } else if (oldNode.firstChild) {
6111
      rule = { rename_tag: DEFAULT_NODE_NAME };
6112
    } else {
6113
      // Remove empty unknown elements
6114
      return null;
6115
    }
6116
6117
    // tests if type condition is met or node should be removed/unwrapped/renamed
6118
    if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
6119
      if (rule.remove_action) {
6120
        if (rule.remove_action === "unwrap") {
6121
          return false;
6122
        } else if (rule.remove_action === "rename") {
6123
          renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
6124
        } else {
6125
          return null;
6126
        }
6127
      } else {
6128
        return null;
6129
      }
6130
    }
6131
6132
    newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
6133
    _handleAttributes(oldNode, newNode, rule, clearInternals);
6134
    _handleStyles(oldNode, newNode, rule);
6135
6136
    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...
6137
6138
    if (newNode.normalize) { newNode.normalize(); }
6139
    return newNode;
6140
  }
6141
6142
  function _testTypes(oldNode, rules, types, clearInternals) {
6143
    var definition, type;
6144
6145
    // do not interfere with placeholder span or pasting caret position is not maintained
6146
    if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml5-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
6147
      return true;
6148
    }
6149
6150
    for (type in types) {
6151
      if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
6152
        definition = rules.type_definitions[type];
6153
        if (_testType(oldNode, definition)) {
6154
          return true;
6155
        }
6156
      }
6157
    }
6158
    return false;
6159
  }
6160
6161
  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...
6162
      var i = a.length;
6163
      while (i--) {
6164
         if (a[i] === obj) {
6165
             return true;
6166
         }
6167
      }
6168
      return false;
6169
  }
6170
6171
  function _testType(oldNode, definition) {
6172
6173
    var nodeClasses = oldNode.getAttribute("class"),
6174
        nodeStyles =  oldNode.getAttribute("style"),
6175
        classesLength, s, s_corrected, a, attr, currentClass, styleProp;
0 ignored issues
show
Unused Code introduced by
The variable currentClass seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable s_corrected seems to be never used. Consider removing it.
Loading history...
6176
6177
    // test for methods
6178
    if (definition.methods) {
6179
      for (var m in definition.methods) {
6180
        if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
6181
6182
          if (typeCeckMethods[m](oldNode)) {
6183
            return true;
6184
          }
6185
        }
6186
      }
6187
    }
6188
6189
    // test for classes, if one found return true
6190
    if (nodeClasses && definition.classes) {
6191
      nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
6192
      classesLength = nodeClasses.length;
6193
      for (var i = 0; i < classesLength; i++) {
6194
        if (definition.classes[nodeClasses[i]]) {
6195
          return true;
6196
        }
6197
      }
6198
    }
6199
6200
    // test for styles, if one found return true
6201
    if (nodeStyles && definition.styles) {
6202
6203
      nodeStyles = nodeStyles.split(';');
6204
      for (s in definition.styles) {
6205
        if (definition.styles.hasOwnProperty(s)) {
6206
          for (var sp = nodeStyles.length; sp--;) {
6207
            styleProp = nodeStyles[sp].split(':');
6208
6209
            if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
6210
              if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml5.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
6211
                return true;
6212
              }
6213
            }
6214
          }
6215
        }
6216
      }
6217
    }
6218
6219
    // test for attributes in general against regex match
6220
    if (definition.attrs) {
6221
        for (a in definition.attrs) {
6222
            if (definition.attrs.hasOwnProperty(a)) {
6223
                attr = wysihtml5.dom.getAttribute(oldNode, a);
6224
                if (typeof(attr) === "string") {
6225
                    if (attr.search(definition.attrs[a]) > -1) {
6226
                        return true;
6227
                    }
6228
                }
6229
            }
6230
        }
6231
    }
6232
    return false;
6233
  }
6234
6235
  function _handleStyles(oldNode, newNode, rule) {
6236
    var s, v;
6237
    if(rule && rule.keep_styles) {
6238
      for (s in rule.keep_styles) {
6239
        if (rule.keep_styles.hasOwnProperty(s)) {
6240
          v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
6241
          // value can be regex and if so should match or style skipped
6242
          if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
6243
            continue;
6244
          }
6245
          if (s === "float") {
6246
            // IE compability
6247
            newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
6248
           } else if (oldNode.style[s]) {
6249
             newNode.style[s] = v;
6250
           }
6251
        }
6252
      }
6253
    }
6254
  };
6255
6256
  function _getAttributesBeginningWith(beginning, attributes) {
6257
    var returnAttributes = [];
6258
    for (var attr in attributes) {
6259
      if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
6260
        returnAttributes.push(attr);
6261
      }
6262
    }
6263
    return returnAttributes;
6264
  }
6265
6266
  function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
6267
    var method = attributeCheckMethods[methodName],
6268
        newAttributeValue;
6269
6270
    if (method) {
6271
      if (attributeValue || (attributeName === "alt" && nodeName == "IMG")) {
6272
        newAttributeValue = method(attributeValue);
6273
        if (typeof(newAttributeValue) === "string") {
6274
          return newAttributeValue;
6275
        }
6276
      }
6277
    }
6278
6279
    return false;
6280
  }
6281
6282
  function _checkAttributes(oldNode, local_attributes) {
6283
    var globalAttributes  = wysihtml5.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
6284
        checkAttributes   = wysihtml5.lang.object(globalAttributes).merge( wysihtml5.lang.object(local_attributes || {}).clone()).get(),
6285
        attributes        = {},
6286
        oldAttributes     = wysihtml5.dom.getAttributes(oldNode),
6287
        attributeName, newValue, matchingAttributes;
6288
6289
    for (attributeName in checkAttributes) {
6290
      if ((/\*$/).test(attributeName)) {
6291
6292
        matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
6293
        for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
6294
6295
          newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
6296
          if (newValue !== false) {
6297
            attributes[matchingAttributes[i]] = newValue;
6298
          }
6299
        }
6300
      } else {
6301
        newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
6302
        if (newValue !== false) {
6303
          attributes[attributeName] = newValue;
6304
        }
6305
      }
6306
    }
6307
6308
    return attributes;
6309
  }
6310
6311
  // TODO: refactor. Too long to read
6312
  function _handleAttributes(oldNode, newNode, rule, clearInternals) {
6313
    var attributes          = {},                         // fresh new set of attributes to set on newNode
6314
        setClass            = rule.set_class,             // classes to set
6315
        addClass            = rule.add_class,             // add classes based on existing attributes
6316
        addStyle            = rule.add_style,             // add styles based on existing attributes
6317
        setAttributes       = rule.set_attributes,        // attributes to set on the current node
6318
        allowedClasses      = currentRules.classes,
6319
        i                   = 0,
6320
        classes             = [],
6321
        styles              = [],
6322
        newClasses          = [],
6323
        oldClasses          = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable oldClasses seems to be never used. Consider removing it.
Loading history...
6324
        classesLength,
6325
        newClassesLength,
0 ignored issues
show
Unused Code introduced by
The variable newClassesLength seems to be never used. Consider removing it.
Loading history...
6326
        currentClass,
6327
        newClass,
6328
        attributeName,
6329
        method;
6330
6331
    if (setAttributes) {
6332
      attributes = wysihtml5.lang.object(setAttributes).clone();
6333
    }
6334
6335
    // check/convert values of attributes
6336
    attributes = wysihtml5.lang.object(attributes).merge(_checkAttributes(oldNode,  rule.check_attributes)).get();
6337
6338
    if (setClass) {
6339
      classes.push(setClass);
6340
    }
6341
6342
    if (addClass) {
6343
      for (attributeName in addClass) {
6344
        method = addClassMethods[addClass[attributeName]];
6345
        if (!method) {
6346
          continue;
6347
        }
6348
        newClass = method(wysihtml5.dom.getAttribute(oldNode, attributeName));
6349
        if (typeof(newClass) === "string") {
6350
          classes.push(newClass);
6351
        }
6352
      }
6353
    }
6354
6355
    if (addStyle) {
6356
      for (attributeName in addStyle) {
6357
        method = addStyleMethods[addStyle[attributeName]];
6358
        if (!method) {
6359
          continue;
6360
        }
6361
6362
        newStyle = method(wysihtml5.dom.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...
6363
        if (typeof(newStyle) === "string") {
6364
          styles.push(newStyle);
6365
        }
6366
      }
6367
    }
6368
6369
6370
    if (typeof(allowedClasses) === "string" && allowedClasses === "any" && oldNode.getAttribute("class")) {
6371
      if (currentRules.classes_blacklist) {
6372
        oldClasses = oldNode.getAttribute("class");
6373
        if (oldClasses) {
6374
          classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
6375
        }
6376
6377
        classesLength = classes.length;
6378
        for (; i<classesLength; i++) {
6379
          currentClass = classes[i];
6380
          if (!currentRules.classes_blacklist[currentClass]) {
6381
            newClasses.push(currentClass);
6382
          }
6383
        }
6384
6385
        if (newClasses.length) {
6386
          attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
6387
        }
6388
6389
      } else {
6390
        attributes["class"] = oldNode.getAttribute("class");
6391
      }
6392
    } else {
6393
      // make sure that wysihtml5 temp class doesn't get stripped out
6394
      if (!clearInternals) {
6395
        allowedClasses["_wysihtml5-temp-placeholder"] = 1;
6396
        allowedClasses["_rangySelectionBoundary"] = 1;
6397
        allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
6398
      }
6399
6400
      // add old classes last
6401
      oldClasses = oldNode.getAttribute("class");
6402
      if (oldClasses) {
6403
        classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
6404
      }
6405
      classesLength = classes.length;
6406
      for (; i<classesLength; i++) {
6407
        currentClass = classes[i];
6408
        if (allowedClasses[currentClass]) {
6409
          newClasses.push(currentClass);
6410
        }
6411
      }
6412
6413
      if (newClasses.length) {
6414
        attributes["class"] = wysihtml5.lang.array(newClasses).unique().join(" ");
6415
      }
6416
    }
6417
6418
    // remove table selection class if present
6419
    if (attributes["class"] && clearInternals) {
6420
      attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
6421
      if ((/^\s*$/g).test(attributes["class"])) {
6422
        delete attributes["class"];
6423
      }
6424
    }
6425
6426
    if (styles.length) {
6427
      attributes["style"] = wysihtml5.lang.array(styles).unique().join(" ");
6428
    }
6429
6430
    // set attributes on newNode
6431
    for (attributeName in attributes) {
6432
      // Setting attributes can cause a js error in IE under certain circumstances
6433
      // eg. on a <img> under https when it's new attribute value is non-https
6434
      // TODO: Investigate this further and check for smarter handling
6435
      try {
6436
        newNode.setAttribute(attributeName, attributes[attributeName]);
6437
      } 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...
6438
    }
6439
6440
    // IE8 sometimes loses the width/height attributes when those are set before the "src"
6441
    // so we make sure to set them again
6442
    if (attributes.src) {
6443
      if (typeof(attributes.width) !== "undefined") {
6444
        newNode.setAttribute("width", attributes.width);
6445
      }
6446
      if (typeof(attributes.height) !== "undefined") {
6447
        newNode.setAttribute("height", attributes.height);
6448
      }
6449
    }
6450
  }
6451
6452
  var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
6453
  function _handleText(oldNode) {
6454
    var nextSibling = oldNode.nextSibling;
6455
    if (nextSibling && nextSibling.nodeType === wysihtml5.TEXT_NODE) {
6456
      // Concatenate text nodes
6457
      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...
6458
    } else {
6459
      // \uFEFF = wysihtml5.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
6460
      var data = oldNode.data.replace(INVISIBLE_SPACE_REG_EXP, "");
6461
      return oldNode.ownerDocument.createTextNode(data);
6462
    }
6463
  }
6464
6465
  function _handleComment(oldNode) {
6466
    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...
6467
      return oldNode.ownerDocument.createComment(oldNode.nodeValue);
6468
    }
6469
  }
6470
6471
  // ------------ attribute checks ------------ \\
6472
  var attributeCheckMethods = {
6473
    url: (function() {
6474
      var REG_EXP = /^https?:\/\//i;
6475
      return function(attributeValue) {
6476
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6477
          return null;
6478
        }
6479
        return attributeValue.replace(REG_EXP, function(match) {
6480
          return match.toLowerCase();
6481
        });
6482
      };
6483
    })(),
6484
6485
    src: (function() {
6486
      var REG_EXP = /^(\/|https?:\/\/)/i;
6487
      return function(attributeValue) {
6488
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6489
          return null;
6490
        }
6491
        return attributeValue.replace(REG_EXP, function(match) {
6492
          return match.toLowerCase();
6493
        });
6494
      };
6495
    })(),
6496
6497
    href: (function() {
6498
      var REG_EXP = /^(#|\/|https?:\/\/|mailto:)/i;
6499
      return function(attributeValue) {
6500
        if (!attributeValue || !attributeValue.match(REG_EXP)) {
6501
          return null;
6502
        }
6503
        return attributeValue.replace(REG_EXP, function(match) {
6504
          return match.toLowerCase();
6505
        });
6506
      };
6507
    })(),
6508
6509
    alt: (function() {
6510
      var REG_EXP = /[^ a-z0-9_\-]/gi;
6511
      return function(attributeValue) {
6512
        if (!attributeValue) {
6513
          return "";
6514
        }
6515
        return attributeValue.replace(REG_EXP, "");
6516
      };
6517
    })(),
6518
6519
    numbers: (function() {
6520
      var REG_EXP = /\D/g;
6521
      return function(attributeValue) {
6522
        attributeValue = (attributeValue || "").replace(REG_EXP, "");
6523
        return attributeValue || null;
6524
      };
6525
    })(),
6526
6527
    any: (function() {
6528
      return function(attributeValue) {
6529
        return attributeValue;
6530
      };
6531
    })()
6532
  };
6533
6534
  // ------------ style converter (converts an html attribute to a style) ------------ \\
6535
  var addStyleMethods = {
6536
    align_text: (function() {
6537
      var mapping = {
6538
        left:     "text-align: left;",
6539
        right:    "text-align: right;",
6540
        center:   "text-align: center;"
6541
      };
6542
      return function(attributeValue) {
6543
        return mapping[String(attributeValue).toLowerCase()];
6544
      };
6545
    })(),
6546
  };
6547
6548
  // ------------ class converter (converts an html attribute to a class name) ------------ \\
6549
  var addClassMethods = {
6550
    align_img: (function() {
6551
      var mapping = {
6552
        left:   "wysiwyg-float-left",
6553
        right:  "wysiwyg-float-right"
6554
      };
6555
      return function(attributeValue) {
6556
        return mapping[String(attributeValue).toLowerCase()];
6557
      };
6558
    })(),
6559
6560
    align_text: (function() {
6561
      var mapping = {
6562
        left:     "wysiwyg-text-align-left",
6563
        right:    "wysiwyg-text-align-right",
6564
        center:   "wysiwyg-text-align-center",
6565
        justify:  "wysiwyg-text-align-justify"
6566
      };
6567
      return function(attributeValue) {
6568
        return mapping[String(attributeValue).toLowerCase()];
6569
      };
6570
    })(),
6571
6572
    clear_br: (function() {
6573
      var mapping = {
6574
        left:   "wysiwyg-clear-left",
6575
        right:  "wysiwyg-clear-right",
6576
        both:   "wysiwyg-clear-both",
6577
        all:    "wysiwyg-clear-both"
6578
      };
6579
      return function(attributeValue) {
6580
        return mapping[String(attributeValue).toLowerCase()];
6581
      };
6582
    })(),
6583
6584
    size_font: (function() {
6585
      var mapping = {
6586
        "1": "wysiwyg-font-size-xx-small",
6587
        "2": "wysiwyg-font-size-small",
6588
        "3": "wysiwyg-font-size-medium",
6589
        "4": "wysiwyg-font-size-large",
6590
        "5": "wysiwyg-font-size-x-large",
6591
        "6": "wysiwyg-font-size-xx-large",
6592
        "7": "wysiwyg-font-size-xx-large",
6593
        "-": "wysiwyg-font-size-smaller",
6594
        "+": "wysiwyg-font-size-larger"
6595
      };
6596
      return function(attributeValue) {
6597
        return mapping[String(attributeValue).charAt(0)];
6598
      };
6599
    })()
6600
  };
6601
6602
  // checks if element is possibly visible
6603
  var typeCeckMethods = {
6604
    has_visible_contet: (function() {
6605
      var txt,
6606
          isVisible = false,
0 ignored issues
show
Unused Code introduced by
The variable isVisible seems to be never used. Consider removing it.
Loading history...
6607
          visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
6608
                             'style', 'table', 'iframe', 'object', 'embed', 'audio',
6609
                             'svg', 'input', 'button', 'select','textarea', 'canvas'];
6610
6611
      return function(el) {
6612
6613
        // has visible innertext. so is visible
6614
        txt = (el.innerText || el.textContent).replace(/\s/g, '');
6615
        if (txt && txt.length > 0) {
6616
          return true;
6617
        }
6618
6619
        // matches list of visible dimensioned elements
6620
        for (var i = visibleElements.length; i--;) {
6621
          if (el.querySelector(visibleElements[i])) {
6622
            return true;
6623
          }
6624
        }
6625
6626
        // try to measure dimesions in last resort. (can find only of elements in dom)
6627
        if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
6628
          return true;
6629
        }
6630
6631
        return false;
6632
      };
6633
    })()
6634
  };
6635
6636
  var elementHandlingMethods = {
6637
    unwrap: function (element) {
6638
      wysihtml5.dom.unwrap(element);
6639
    },
6640
6641
    remove: function (element) {
6642
      element.parentNode.removeChild(element);
6643
    }
6644
  };
6645
6646
  return parse(elementOrHtml_current, config_current);
6647
};
6648
;/**
6649
 * Checks for empty text node childs and removes them
6650
 *
6651
 * @param {Element} node The element in which to cleanup
6652
 * @example
6653
 *    wysihtml5.dom.removeEmptyTextNodes(element);
6654
 */
6655
wysihtml5.dom.removeEmptyTextNodes = function(node) {
6656
  var childNode,
6657
      childNodes        = wysihtml5.lang.array(node.childNodes).get(),
6658
      childNodesLength  = childNodes.length,
6659
      i                 = 0;
6660
  for (; i<childNodesLength; i++) {
6661
    childNode = childNodes[i];
6662
    if (childNode.nodeType === wysihtml5.TEXT_NODE && childNode.data === "") {
6663
      childNode.parentNode.removeChild(childNode);
6664
    }
6665
  }
6666
};
6667
;/**
6668
 * Renames an element (eg. a <div> to a <p>) and keeps its childs
6669
 *
6670
 * @param {Element} element The list element which should be renamed
6671
 * @param {Element} newNodeName The desired tag name
6672
 *
6673
 * @example
6674
 *    <!-- Assume the following dom: -->
6675
 *    <ul id="list">
6676
 *      <li>eminem</li>
6677
 *      <li>dr. dre</li>
6678
 *      <li>50 Cent</li>
6679
 *    </ul>
6680
 *
6681
 *    <script>
6682
 *      wysihtml5.dom.renameElement(document.getElementById("list"), "ol");
6683
 *    </script>
6684
 *
6685
 *    <!-- Will result in: -->
6686
 *    <ol>
6687
 *      <li>eminem</li>
6688
 *      <li>dr. dre</li>
6689
 *      <li>50 Cent</li>
6690
 *    </ol>
6691
 */
6692
wysihtml5.dom.renameElement = function(element, newNodeName) {
6693
  var newElement = element.ownerDocument.createElement(newNodeName),
6694
      firstChild;
6695
  while (firstChild = element.firstChild) {
6696
    newElement.appendChild(firstChild);
6697
  }
6698
  wysihtml5.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
6699
  element.parentNode.replaceChild(newElement, element);
6700
  return newElement;
6701
};
6702
;/**
6703
 * Takes an element, removes it and replaces it with it's childs
6704
 *
6705
 * @param {Object} node The node which to replace with it's child nodes
6706
 * @example
6707
 *    <div id="foo">
6708
 *      <span>hello</span>
6709
 *    </div>
6710
 *    <script>
6711
 *      // Remove #foo and replace with it's children
6712
 *      wysihtml5.dom.replaceWithChildNodes(document.getElementById("foo"));
6713
 *    </script>
6714
 */
6715
wysihtml5.dom.replaceWithChildNodes = function(node) {
6716
  if (!node.parentNode) {
6717
    return;
6718
  }
6719
6720
  if (!node.firstChild) {
6721
    node.parentNode.removeChild(node);
6722
    return;
6723
  }
6724
6725
  var fragment = node.ownerDocument.createDocumentFragment();
6726
  while (node.firstChild) {
6727
    fragment.appendChild(node.firstChild);
6728
  }
6729
  node.parentNode.replaceChild(fragment, node);
6730
  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...
6731
};
6732
;/**
6733
 * Unwraps an unordered/ordered list
6734
 *
6735
 * @param {Element} element The list element which should be unwrapped
0 ignored issues
show
Documentation introduced by
The parameter element does not exist. Did you maybe forget to remove this comment?
Loading history...
6736
 *
6737
 * @example
6738
 *    <!-- Assume the following dom: -->
6739
 *    <ul id="list">
6740
 *      <li>eminem</li>
6741
 *      <li>dr. dre</li>
6742
 *      <li>50 Cent</li>
6743
 *    </ul>
6744
 *
6745
 *    <script>
6746
 *      wysihtml5.dom.resolveList(document.getElementById("list"));
6747
 *    </script>
6748
 *
6749
 *    <!-- Will result in: -->
6750
 *    eminem<br>
6751
 *    dr. dre<br>
6752
 *    50 Cent<br>
6753
 */
6754
(function(dom) {
6755
  function _isBlockElement(node) {
6756
    return dom.getStyle("display").from(node) === "block";
6757
  }
6758
6759
  function _isLineBreak(node) {
6760
    return node.nodeName === "BR";
6761
  }
6762
6763
  function _appendLineBreak(element) {
6764
    var lineBreak = element.ownerDocument.createElement("br");
6765
    element.appendChild(lineBreak);
6766
  }
6767
6768
  function resolveList(list, useLineBreaks) {
6769
    if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
6770
      return;
6771
    }
6772
6773
    var doc             = list.ownerDocument,
6774
        fragment        = doc.createDocumentFragment(),
6775
        previousSibling = wysihtml5.dom.domNode(list).prev({ignoreBlankTexts: true}),
6776
        firstChild,
6777
        lastChild,
6778
        isLastChild,
6779
        shouldAppendLineBreak,
6780
        paragraph,
6781
        listItem;
6782
6783
    if (useLineBreaks) {
6784
      // Insert line break if list is after a non-block element
6785
      if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) {
6786
        _appendLineBreak(fragment);
6787
      }
6788
6789
      while (listItem = (list.firstElementChild || list.firstChild)) {
6790
        lastChild = listItem.lastChild;
6791
        while (firstChild = listItem.firstChild) {
6792
          isLastChild           = firstChild === lastChild;
6793
          // This needs to be done before appending it to the fragment, as it otherwise will lose style information
6794
          shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
6795
          fragment.appendChild(firstChild);
6796
          if (shouldAppendLineBreak) {
6797
            _appendLineBreak(fragment);
6798
          }
6799
        }
6800
6801
        listItem.parentNode.removeChild(listItem);
6802
      }
6803
    } else {
6804
      while (listItem = (list.firstElementChild || list.firstChild)) {
6805
        if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
6806
          while (firstChild = listItem.firstChild) {
6807
            fragment.appendChild(firstChild);
6808
          }
6809
        } else {
6810
          paragraph = doc.createElement("p");
6811
          while (firstChild = listItem.firstChild) {
6812
            paragraph.appendChild(firstChild);
6813
          }
6814
          fragment.appendChild(paragraph);
6815
        }
6816
        listItem.parentNode.removeChild(listItem);
6817
      }
6818
    }
6819
6820
    list.parentNode.replaceChild(fragment, list);
6821
  }
6822
6823
  dom.resolveList = resolveList;
6824
})(wysihtml5.dom);
6825
;/**
6826
 * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
6827
 *
6828
 * Browser Compatibility:
6829
 *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
6830
 *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
6831
 *
6832
 * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
6833
 *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
6834
 *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
6835
 *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
6836
 *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
6837
 *      can do anything as if the sandbox attribute wasn't set
6838
 *
6839
 * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
0 ignored issues
show
Documentation introduced by
The parameter readyCallback does not exist. Did you maybe forget to remove this comment?
Loading history...
6840
 * @param {Object} [config] Optional parameters
0 ignored issues
show
Documentation introduced by
The parameter config does not exist. Did you maybe forget to remove this comment?
Loading history...
6841
 *
6842
 * @example
6843
 *    new wysihtml5.dom.Sandbox(function(sandbox) {
6844
 *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
6845
 *    });
6846
 */
6847
(function(wysihtml5) {
6848
  var /**
6849
       * Default configuration
6850
       */
6851
      doc                 = document,
6852
      /**
6853
       * Properties to unset/protect on the window object
6854
       */
6855
      windowProperties    = [
6856
        "parent", "top", "opener", "frameElement", "frames",
6857
        "localStorage", "globalStorage", "sessionStorage", "indexedDB"
6858
      ],
6859
      /**
6860
       * Properties on the window object which are set to an empty function
6861
       */
6862
      windowProperties2   = [
6863
        "open", "close", "openDialog", "showModalDialog",
6864
        "alert", "confirm", "prompt",
6865
        "openDatabase", "postMessage",
6866
        "XMLHttpRequest", "XDomainRequest"
6867
      ],
6868
      /**
6869
       * Properties to unset/protect on the document object
6870
       */
6871
      documentProperties  = [
6872
        "referrer",
6873
        "write", "open", "close"
6874
      ];
6875
6876
  wysihtml5.dom.Sandbox = Base.extend(
6877
    /** @scope wysihtml5.dom.Sandbox.prototype */ {
6878
6879
    constructor: function(readyCallback, config) {
6880
      this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
6881
      this.config   = wysihtml5.lang.object({}).merge(config).get();
6882
      this.editableArea   = this._createIframe();
6883
    },
6884
6885
    insertInto: function(element) {
6886
      if (typeof(element) === "string") {
6887
        element = doc.getElementById(element);
6888
      }
6889
6890
      element.appendChild(this.editableArea);
6891
    },
6892
6893
    getIframe: function() {
6894
      return this.editableArea;
6895
    },
6896
6897
    getWindow: function() {
6898
      this._readyError();
6899
    },
6900
6901
    getDocument: function() {
6902
      this._readyError();
6903
    },
6904
6905
    destroy: function() {
6906
      var iframe = this.getIframe();
6907
      iframe.parentNode.removeChild(iframe);
6908
    },
6909
6910
    _readyError: function() {
6911
      throw new Error("wysihtml5.Sandbox: Sandbox iframe isn't loaded yet");
6912
    },
6913
6914
    /**
6915
     * Creates the sandbox iframe
6916
     *
6917
     * Some important notes:
6918
     *  - We can't use HTML5 sandbox for now:
6919
     *    setting it causes that the iframe's dom can't be accessed from the outside
6920
     *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
6921
     *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
6922
     *    In order to make this happen we need to set the "allow-scripts" flag.
6923
     *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
6924
     *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
6925
     *  - IE needs to have the security="restricted" attribute set before the iframe is
6926
     *    inserted into the dom tree
6927
     *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
6928
     *    though it supports it
6929
     *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
6930
     *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
6931
     *    on the onreadystatechange event
6932
     */
6933
    _createIframe: function() {
6934
      var that   = this,
6935
          iframe = doc.createElement("iframe");
6936
      iframe.className = "wysihtml5-sandbox";
6937
      wysihtml5.dom.setAttributes({
6938
        "security":           "restricted",
6939
        "allowtransparency":  "true",
6940
        "frameborder":        0,
6941
        "width":              0,
6942
        "height":             0,
6943
        "marginwidth":        0,
6944
        "marginheight":       0
6945
      }).on(iframe);
6946
6947
      // Setting the src like this prevents ssl warnings in IE6
6948
      if (wysihtml5.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
6949
        iframe.src = "javascript:'<html></html>'";
6950
      }
6951
6952
      iframe.onload = function() {
6953
        iframe.onreadystatechange = iframe.onload = null;
6954
        that._onLoadIframe(iframe);
6955
      };
6956
6957
      iframe.onreadystatechange = function() {
6958
        if (/loaded|complete/.test(iframe.readyState)) {
6959
          iframe.onreadystatechange = iframe.onload = null;
6960
          that._onLoadIframe(iframe);
6961
        }
6962
      };
6963
6964
      return iframe;
6965
    },
6966
6967
    /**
6968
     * Callback for when the iframe has finished loading
6969
     */
6970
    _onLoadIframe: function(iframe) {
6971
      // don't resume when the iframe got unloaded (eg. by removing it from the dom)
6972
      if (!wysihtml5.dom.contains(doc.documentElement, iframe)) {
6973
        return;
6974
      }
6975
6976
      var that           = this,
6977
          iframeWindow   = iframe.contentWindow,
6978
          iframeDocument = iframe.contentWindow.document,
6979
          charset        = doc.characterSet || doc.charset || "utf-8",
6980
          sandboxHtml    = this._getHtml({
6981
            charset:      charset,
6982
            stylesheets:  this.config.stylesheets
6983
          });
6984
6985
      // Create the basic dom tree including proper DOCTYPE and charset
6986
      iframeDocument.open("text/html", "replace");
6987
      iframeDocument.write(sandboxHtml);
6988
      iframeDocument.close();
6989
6990
      this.getWindow = function() { return iframe.contentWindow; };
6991
      this.getDocument = function() { return iframe.contentWindow.document; };
6992
6993
      // Catch js errors and pass them to the parent's onerror event
6994
      // addEventListener("error") doesn't work properly in some browsers
6995
      // TODO: apparently this doesn't work in IE9!
6996
      iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
6997
        throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
6998
      };
6999
7000
      if (!wysihtml5.browser.supportsSandboxedIframes()) {
7001
        // Unset a bunch of sensitive variables
7002
        // Please note: This isn't hack safe!
7003
        // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
7004
        // IE is secure though, which is the most important thing, since IE is the only browser, who
7005
        // takes over scripts & styles into contentEditable elements when copied from external websites
7006
        // or applications (Microsoft Word, ...)
7007
        var i, length;
7008
        for (i=0, length=windowProperties.length; i<length; i++) {
7009
          this._unset(iframeWindow, windowProperties[i]);
7010
        }
7011
        for (i=0, length=windowProperties2.length; i<length; i++) {
7012
          this._unset(iframeWindow, windowProperties2[i], wysihtml5.EMPTY_FUNCTION);
7013
        }
7014
        for (i=0, length=documentProperties.length; i<length; i++) {
7015
          this._unset(iframeDocument, documentProperties[i]);
7016
        }
7017
        // This doesn't work in Safari 5
7018
        // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
7019
        this._unset(iframeDocument, "cookie", "", true);
7020
      }
7021
7022
      this.loaded = true;
7023
7024
      // Trigger the callback
7025
      setTimeout(function() { that.callback(that); }, 0);
7026
    },
7027
7028
    _getHtml: function(templateVars) {
7029
      var stylesheets = templateVars.stylesheets,
7030
          html        = "",
7031
          i           = 0,
7032
          length;
7033
      stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
7034
      if (stylesheets) {
7035
        length = stylesheets.length;
7036
        for (; i<length; i++) {
7037
          html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
7038
        }
7039
      }
7040
      templateVars.stylesheets = html;
7041
7042
      return wysihtml5.lang.string(
7043
        '<!DOCTYPE html><html><head>'
7044
        + '<meta charset="#{charset}">#{stylesheets}</head>'
7045
        + '<body></body></html>'
7046
      ).interpolate(templateVars);
7047
    },
7048
7049
    /**
7050
     * Method to unset/override existing variables
7051
     * @example
7052
     *    // Make cookie unreadable and unwritable
7053
     *    this._unset(document, "cookie", "", true);
7054
     */
7055
    _unset: function(object, property, value, setter) {
7056
      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...
7057
7058
      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...
7059
      if (setter) {
7060
        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...
7061
      }
7062
7063
      if (!wysihtml5.browser.crashesWhenDefineProperty(property)) {
7064
        try {
7065
          var config = {
7066
            get: function() { return value; }
7067
          };
7068
          if (setter) {
7069
            config.set = function() {};
7070
          }
7071
          Object.defineProperty(object, property, config);
7072
        } 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...
7073
      }
7074
    }
7075
  });
7076
})(wysihtml5);
7077
;(function(wysihtml5) {
7078
  var doc = document;
7079
  wysihtml5.dom.ContentEditableArea = Base.extend({
7080
      getContentEditable: function() {
7081
        return this.element;
7082
      },
7083
7084
      getWindow: function() {
7085
        return this.element.ownerDocument.defaultView;
7086
      },
7087
7088
      getDocument: function() {
7089
        return this.element.ownerDocument;
7090
      },
7091
7092
      constructor: function(readyCallback, config, contentEditable) {
7093
        this.callback = readyCallback || wysihtml5.EMPTY_FUNCTION;
7094
        this.config   = wysihtml5.lang.object({}).merge(config).get();
7095
        if (contentEditable) {
7096
            this.element = this._bindElement(contentEditable);
7097
        } else {
7098
            this.element = this._createElement();
7099
        }
7100
      },
7101
7102
      // creates a new contenteditable and initiates it
7103
      _createElement: function() {
7104
        var element = doc.createElement("div");
7105
        element.className = "wysihtml5-sandbox";
7106
        this._loadElement(element);
7107
        return element;
7108
      },
7109
7110
      // initiates an allready existent contenteditable
7111
      _bindElement: function(contentEditable) {
7112
        contentEditable.className = (contentEditable.className && contentEditable.className != '') ? contentEditable.className + " wysihtml5-sandbox" : "wysihtml5-sandbox";
7113
        this._loadElement(contentEditable, true);
7114
        return contentEditable;
7115
      },
7116
7117
      _loadElement: function(element, contentExists) {
7118
          var that = this;
7119
        if (!contentExists) {
7120
            var sandboxHtml = this._getHtml();
7121
            element.innerHTML = sandboxHtml;
7122
        }
7123
7124
        this.getWindow = function() { return element.ownerDocument.defaultView; };
7125
        this.getDocument = function() { return element.ownerDocument; };
7126
7127
        // Catch js errors and pass them to the parent's onerror event
7128
        // addEventListener("error") doesn't work properly in some browsers
7129
        // TODO: apparently this doesn't work in IE9!
7130
        // TODO: figure out and bind the errors logic for contenteditble mode
7131
        /*iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
7132
          throw new Error("wysihtml5.Sandbox: " + errorMessage, fileName, lineNumber);
7133
        }
7134
        */
7135
        this.loaded = true;
7136
        // Trigger the callback
7137
        setTimeout(function() { that.callback(that); }, 0);
7138
      },
7139
7140
      _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...
7141
        return '';
7142
      }
7143
7144
  });
7145
})(wysihtml5);
7146
;(function() {
7147
  var mapping = {
7148
    "className": "class"
7149
  };
7150
  wysihtml5.dom.setAttributes = function(attributes) {
7151
    return {
7152
      on: function(element) {
7153
        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...
7154
          element.setAttribute(mapping[i] || i, attributes[i]);
7155
        }
7156
      }
7157
    };
7158
  };
7159
})();
7160
;wysihtml5.dom.setStyles = function(styles) {
7161
  return {
7162
    on: function(element) {
7163
      var style = element.style;
7164
      if (typeof(styles) === "string") {
7165
        style.cssText += ";" + styles;
7166
        return;
7167
      }
7168
      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...
7169
        if (i === "float") {
7170
          style.cssFloat = styles[i];
7171
          style.styleFloat = styles[i];
7172
        } else {
7173
          style[i] = styles[i];
7174
        }
7175
      }
7176
    }
7177
  };
7178
};
7179
;/**
7180
 * Simulate HTML5 placeholder attribute
7181
 *
7182
 * Needed since
7183
 *    - div[contentEditable] elements don't support it
7184
 *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all
7185
 *
7186
 * @param {Object} parent Instance of main wysihtml5.Editor class
0 ignored issues
show
Documentation introduced by
The parameter parent does not exist. Did you maybe forget to remove this comment?
Loading history...
7187
 * @param {Element} view Instance of wysihtml5.views.* class
0 ignored issues
show
Documentation introduced by
The parameter view does not exist. Did you maybe forget to remove this comment?
Loading history...
7188
 * @param {String} placeholderText
0 ignored issues
show
Documentation introduced by
The parameter placeholderText does not exist. Did you maybe forget to remove this comment?
Loading history...
7189
 *
7190
 * @example
7191
 *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
7192
 */
7193
(function(dom) {
7194
  dom.simulatePlaceholder = function(editor, view, placeholderText) {
7195
    var CLASS_NAME = "placeholder",
7196
        unset = function() {
7197
          var composerIsVisible   = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
7198
          if (view.hasPlaceholderSet()) {
7199
            view.clear();
7200
            view.element.focus();
7201
            if (composerIsVisible ) {
7202
              setTimeout(function() {
7203
                var sel = view.selection.getSelection();
7204
                if (!sel.focusNode || !sel.anchorNode) {
7205
                  view.selection.selectNode(view.element.firstChild || view.element);
7206
                }
7207
              }, 0);
7208
            }
7209
          }
7210
          view.placeholderSet = false;
7211
          dom.removeClass(view.element, CLASS_NAME);
7212
        },
7213
        set = function() {
7214
          if (view.isEmpty()) {
7215
            view.placeholderSet = true;
7216
            view.setValue(placeholderText);
7217
            dom.addClass(view.element, CLASS_NAME);
7218
          }
7219
        };
7220
7221
    editor
7222
      .on("set_placeholder", set)
7223
      .on("unset_placeholder", unset)
7224
      .on("focus:composer", unset)
7225
      .on("paste:composer", unset)
7226
      .on("blur:composer", set);
7227
7228
    set();
7229
  };
7230
})(wysihtml5.dom);
7231
;(function(dom) {
7232
  var documentElement = document.documentElement;
7233
  if ("textContent" in documentElement) {
7234
    dom.setTextContent = function(element, text) {
7235
      element.textContent = text;
7236
    };
7237
7238
    dom.getTextContent = function(element) {
7239
      return element.textContent;
7240
    };
7241
  } else if ("innerText" in documentElement) {
7242
    dom.setTextContent = function(element, text) {
7243
      element.innerText = text;
7244
    };
7245
7246
    dom.getTextContent = function(element) {
7247
      return element.innerText;
7248
    };
7249
  } else {
7250
    dom.setTextContent = function(element, text) {
7251
      element.nodeValue = text;
7252
    };
7253
7254
    dom.getTextContent = function(element) {
7255
      return element.nodeValue;
7256
    };
7257
  }
7258
})(wysihtml5.dom);
7259
7260
;/**
7261
 * Get a set of attribute from one element
7262
 *
7263
 * IE gives wrong results for hasAttribute/getAttribute, for example:
7264
 *    var td = document.createElement("td");
7265
 *    td.getAttribute("rowspan"); // => "1" in IE
7266
 *
7267
 * Therefore we have to check the element's outerHTML for the attribute
7268
*/
7269
7270
wysihtml5.dom.getAttribute = function(node, attributeName) {
7271
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly();
7272
  attributeName = attributeName.toLowerCase();
7273
  var nodeName = node.nodeName;
7274
  if (nodeName == "IMG" && attributeName == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
7275
    // Get 'src' attribute value via object property since this will always contain the
7276
    // full absolute url (http://...)
7277
    // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
7278
    // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
7279
    return node.src;
7280
  } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
7281
    // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
7282
    var outerHTML      = node.outerHTML.toLowerCase(),
7283
        // TODO: This might not work for attributes without value: <input disabled>
7284
        hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1;
7285
7286
    return hasAttribute ? node.getAttribute(attributeName) : null;
7287
  } else{
7288
    return node.getAttribute(attributeName);
7289
  }
7290
};
7291
;/**
7292
 * Get all attributes of an element
7293
 *
7294
 * IE gives wrong results for hasAttribute/getAttribute, for example:
7295
 *    var td = document.createElement("td");
7296
 *    td.getAttribute("rowspan"); // => "1" in IE
7297
 *
7298
 * Therefore we have to check the element's outerHTML for the attribute
7299
*/
7300
7301
wysihtml5.dom.getAttributes = function(node) {
7302
  var HAS_GET_ATTRIBUTE_BUG = !wysihtml5.browser.supportsGetAttributeCorrectly(),
7303
      nodeName = node.nodeName,
7304
      attributes = [],
7305
      attr;
7306
7307
  for (attr in node.attributes) {
7308
    if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr)))  {
7309
      if (node.attributes[attr].specified) {
7310
        if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml5.dom.isLoadedImage(node) === true) {
7311
          attributes['src'] = node.src;
7312
        } else if (wysihtml5.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
7313
          if (node.attributes[attr].value !== 1) {
7314
            attributes[node.attributes[attr].name] = node.attributes[attr].value;
7315
          }
7316
        } else {
7317
          attributes[node.attributes[attr].name] = node.attributes[attr].value;
7318
        }
7319
      }
7320
    }
7321
  }
7322
  return attributes;
7323
};;/**
7324
   * Check whether the given node is a proper loaded image
7325
   * FIXME: Returns undefined when unknown (Chrome, Safari)
7326
*/
7327
7328
wysihtml5.dom.isLoadedImage = function (node) {
7329
  try {
7330
    return node.complete && !node.mozMatchesSelector(":-moz-broken");
7331
  } catch(e) {
7332
    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...
7333
      return true;
7334
    }
7335
  }
7336
};
7337
;(function(wysihtml5) {
7338
7339
    var api = wysihtml5.dom;
7340
7341
    var MapCell = function(cell) {
7342
      this.el = cell;
7343
      this.isColspan= false;
7344
      this.isRowspan= false;
7345
      this.firstCol= true;
7346
      this.lastCol= true;
7347
      this.firstRow= true;
7348
      this.lastRow= true;
7349
      this.isReal= true;
7350
      this.spanCollection= [];
7351
      this.modified = false;
7352
    };
7353
7354
    var TableModifyerByCell = function (cell, table) {
7355
        if (cell) {
7356
            this.cell = cell;
7357
            this.table = api.getParentElement(cell, { nodeName: ["TABLE"] });
7358
        } else if (table) {
7359
            this.table = table;
7360
            this.cell = this.table.querySelectorAll('th, td')[0];
7361
        }
7362
    };
7363
7364
    function queryInList(list, query) {
7365
        var ret = [],
7366
            q;
7367
        for (var e = 0, len = list.length; e < len; e++) {
7368
            q = list[e].querySelectorAll(query);
7369
            if (q) {
7370
                for(var i = q.length; i--; ret.unshift(q[i]));
0 ignored issues
show
introduced by
The for loop does not have a body. Maybe you have misplaced a semicolon. If you do wish to have a loop without a body, use an empty body {}.
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

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

Consider:

if (a > 0)
    b = 42;

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

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

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

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

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

Loading history...
7371
            }
7372
        }
7373
        return ret;
7374
    }
7375
7376
    function removeElement(el) {
7377
        el.parentNode.removeChild(el);
7378
    }
7379
7380
    function insertAfter(referenceNode, newNode) {
7381
        referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
7382
    }
7383
7384
    function nextNode(node, tag) {
7385
        var element = node.nextSibling;
7386
        while (element.nodeType !=1) {
7387
            element = element.nextSibling;
7388
            if (!tag || tag == element.tagName.toLowerCase()) {
7389
                return element;
7390
            }
7391
        }
7392
        return null;
7393
    }
7394
7395
    TableModifyerByCell.prototype = {
7396
7397
        addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) {
7398
            var spanCollect = [],
7399
                rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0),
7400
                cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0);
7401
7402
            for (var rr = r; rr <= rmax; rr++) {
7403
                if (typeof map[rr] == "undefined") { map[rr] = []; }
7404
                for (var cc = c; cc <= cmax; cc++) {
7405
                    map[rr][cc] = new MapCell(cell);
7406
                    map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1);
7407
                    map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1);
7408
                    map[rr][cc].firstCol = cc == c;
7409
                    map[rr][cc].lastCol = cc == cmax;
7410
                    map[rr][cc].firstRow = rr == r;
7411
                    map[rr][cc].lastRow = rr == rmax;
7412
                    map[rr][cc].isReal = cc == c && rr == r;
7413
                    map[rr][cc].spanCollection = spanCollect;
7414
7415
                    spanCollect.push(map[rr][cc]);
7416
                }
7417
            }
7418
        },
7419
7420
        setCellAsModified: function(cell) {
7421
            cell.modified = true;
7422
            if (cell.spanCollection.length > 0) {
7423
              for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) {
7424
                cell.spanCollection[s].modified = true;
7425
              }
7426
            }
7427
        },
7428
7429
        setTableMap: function() {
7430
            var map = [];
7431
            var tableRows = this.getTableRows(),
7432
                ridx, row, cells, cidx, cell,
7433
                c,
7434
                cspan, rspan;
7435
7436
            for (ridx = 0; ridx < tableRows.length; ridx++) {
7437
                row = tableRows[ridx];
7438
                cells = this.getRowCells(row);
7439
                c = 0;
7440
                if (typeof map[ridx] == "undefined") { map[ridx] = []; }
7441
                for (cidx = 0; cidx < cells.length; cidx++) {
7442
                    cell = cells[cidx];
7443
7444
                    // If cell allready set means it is set by col or rowspan,
7445
                    // so increase cols index until free col is found
7446
                    while (typeof map[ridx][c] != "undefined") { c++; }
7447
7448
                    cspan = api.getAttribute(cell, 'colspan');
7449
                    rspan = api.getAttribute(cell, 'rowspan');
7450
7451
                    if (cspan || rspan) {
7452
                        this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan);
7453
                        c = c + ((cspan) ? parseInt(cspan, 10) : 1);
7454
                    } else {
7455
                        map[ridx][c] = new MapCell(cell);
7456
                        c++;
7457
                    }
7458
                }
7459
            }
7460
            this.map = map;
7461
            return map;
7462
        },
7463
7464
        getRowCells: function(row) {
7465
            var inlineTables = this.table.querySelectorAll('table'),
7466
                inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [],
7467
                allCells = row.querySelectorAll('th, td'),
7468
                tableCells = (inlineCells.length > 0) ? wysihtml5.lang.array(allCells).without(inlineCells) : allCells;
7469
7470
            return tableCells;
7471
        },
7472
7473
        getTableRows: function() {
7474
          var inlineTables = this.table.querySelectorAll('table'),
7475
              inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [],
7476
              allRows = this.table.querySelectorAll('tr'),
7477
              tableRows = (inlineRows.length > 0) ? wysihtml5.lang.array(allRows).without(inlineRows) : allRows;
7478
7479
          return tableRows;
7480
        },
7481
7482
        getMapIndex: function(cell) {
7483
          var r_length = this.map.length,
7484
              c_length = (this.map && this.map[0]) ? this.map[0].length : 0;
7485
7486
          for (var r_idx = 0;r_idx < r_length; r_idx++) {
7487
              for (var c_idx = 0;c_idx < c_length; c_idx++) {
7488
                  if (this.map[r_idx][c_idx].el === cell) {
7489
                      return {'row': r_idx, 'col': c_idx};
7490
                  }
7491
              }
7492
          }
7493
          return false;
7494
        },
7495
7496
        getElementAtIndex: function(idx) {
7497
            this.setTableMap();
7498
            if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) {
7499
                return this.map[idx.row][idx.col].el;
7500
            }
7501
            return null;
7502
        },
7503
7504
        getMapElsTo: function(to_cell) {
7505
            var els = [];
7506
            this.setTableMap();
7507
            this.idx_start = this.getMapIndex(this.cell);
7508
            this.idx_end = this.getMapIndex(to_cell);
7509
7510
            // switch indexes if start is bigger than end
7511
            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)) {
7512
                var temp_idx = this.idx_start;
7513
                this.idx_start = this.idx_end;
7514
                this.idx_end = temp_idx;
7515
            }
7516
            if (this.idx_start.col > this.idx_end.col) {
7517
                var temp_cidx = this.idx_start.col;
7518
                this.idx_start.col = this.idx_end.col;
7519
                this.idx_end.col = temp_cidx;
7520
            }
7521
7522
            if (this.idx_start != null && this.idx_end != null) {
7523
                for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7524
                    for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7525
                        els.push(this.map[row][col].el);
7526
                    }
7527
                }
7528
            }
7529
            return els;
7530
        },
7531
7532
        orderSelectionEnds: function(secondcell) {
7533
            this.setTableMap();
7534
            this.idx_start = this.getMapIndex(this.cell);
7535
            this.idx_end = this.getMapIndex(secondcell);
7536
7537
            // switch indexes if start is bigger than end
7538
            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)) {
7539
                var temp_idx = this.idx_start;
7540
                this.idx_start = this.idx_end;
7541
                this.idx_end = temp_idx;
7542
            }
7543
            if (this.idx_start.col > this.idx_end.col) {
7544
                var temp_cidx = this.idx_start.col;
7545
                this.idx_start.col = this.idx_end.col;
7546
                this.idx_end.col = temp_cidx;
7547
            }
7548
7549
            return {
7550
                "start": this.map[this.idx_start.row][this.idx_start.col].el,
7551
                "end": this.map[this.idx_end.row][this.idx_end.col].el
7552
            };
7553
        },
7554
7555
        createCells: function(tag, nr, attrs) {
7556
            var doc = this.table.ownerDocument,
7557
                frag = doc.createDocumentFragment(),
7558
                cell;
7559
            for (var i = 0; i < nr; i++) {
7560
                cell = doc.createElement(tag);
7561
7562
                if (attrs) {
7563
                    for (var attr in attrs) {
7564
                        if (attrs.hasOwnProperty(attr)) {
7565
                            cell.setAttribute(attr, attrs[attr]);
7566
                        }
7567
                    }
7568
                }
7569
7570
                // add non breaking space
7571
                cell.appendChild(document.createTextNode("\u00a0"));
7572
7573
                frag.appendChild(cell);
7574
            }
7575
            return frag;
7576
        },
7577
7578
        // 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
7579
        correctColIndexForUnreals: function(col, row) {
7580
            var r = this.map[row],
7581
                corrIdx = -1;
7582
            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...
7583
                if (r[i].isReal){
7584
                    corrIdx++;
7585
                }
7586
            }
7587
            return corrIdx;
7588
        },
7589
7590
        getLastNewCellOnRow: function(row, rowLimit) {
7591
            var cells = this.getRowCells(row),
7592
                cell, idx;
7593
7594
            for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) {
7595
                cell = cells[cidx];
7596
                idx = this.getMapIndex(cell);
7597
                if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) {
7598
                    return cell;
7599
                }
7600
            }
7601
            return null;
7602
        },
7603
7604
        removeEmptyTable: function() {
7605
            var cells = this.table.querySelectorAll('td, th');
7606
            if (!cells || cells.length == 0) {
7607
                removeElement(this.table);
7608
                return true;
7609
            } else {
7610
                return false;
7611
            }
7612
        },
7613
7614
        // Splits merged cell on row to unique cells
7615
        splitRowToCells: function(cell) {
7616
            if (cell.isColspan) {
7617
                var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10),
7618
                    cType = cell.el.tagName.toLowerCase();
7619
                if (colspan > 1) {
7620
                    var newCells = this.createCells(cType, colspan -1);
7621
                    insertAfter(cell.el, newCells);
7622
                }
7623
                cell.el.removeAttribute('colspan');
7624
            }
7625
        },
7626
7627
        getRealRowEl: function(force, idx) {
7628
            var r = null,
7629
                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...
7630
7631
            idx = idx || this.idx;
7632
7633
            for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) {
7634
                c = this.map[idx.row][cidx];
7635
                if (c.isReal) {
7636
                    r = api.getParentElement(c.el, { nodeName: ["TR"] });
7637
                    if (r) {
7638
                        return r;
7639
                    }
7640
                }
7641
            }
7642
7643
            if (r === null && force) {
7644
                r = api.getParentElement(this.map[idx.row][idx.col].el, { nodeName: ["TR"] }) || null;
7645
            }
7646
7647
            return r;
7648
        },
7649
7650
        injectRowAt: function(row, col, colspan, cType, c) {
7651
            var r = this.getRealRowEl(false, {'row': row, 'col': col}),
7652
                new_cells = this.createCells(cType, colspan);
7653
7654
            if (r) {
7655
                var n_cidx = this.correctColIndexForUnreals(col, row);
7656
                if (n_cidx >= 0) {
7657
                    insertAfter(this.getRowCells(r)[n_cidx], new_cells);
7658
                } else {
7659
                    r.insertBefore(new_cells, r.firstChild);
7660
                }
7661
            } else {
7662
                var rr = this.table.ownerDocument.createElement('tr');
7663
                rr.appendChild(new_cells);
7664
                insertAfter(api.getParentElement(c.el, { nodeName: ["TR"] }), rr);
7665
            }
7666
        },
7667
7668
        canMerge: function(to) {
7669
            this.to = to;
7670
            this.setTableMap();
7671
            this.idx_start = this.getMapIndex(this.cell);
7672
            this.idx_end = this.getMapIndex(this.to);
7673
7674
            // switch indexes if start is bigger than end
7675
            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)) {
7676
                var temp_idx = this.idx_start;
7677
                this.idx_start = this.idx_end;
7678
                this.idx_end = temp_idx;
7679
            }
7680
            if (this.idx_start.col > this.idx_end.col) {
7681
                var temp_cidx = this.idx_start.col;
7682
                this.idx_start.col = this.idx_end.col;
7683
                this.idx_end.col = temp_cidx;
7684
            }
7685
7686
            for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7687
                for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7688
                    if (this.map[row][col].isColspan || this.map[row][col].isRowspan) {
7689
                        return false;
7690
                    }
7691
                }
7692
            }
7693
            return true;
7694
        },
7695
7696
        decreaseCellSpan: function(cell, span) {
7697
            var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1;
7698
            if (nr >= 1) {
7699
                cell.el.setAttribute(span, nr);
7700
            } else {
7701
                cell.el.removeAttribute(span);
7702
                if (span == 'colspan') {
7703
                    cell.isColspan = false;
7704
                }
7705
                if (span == 'rowspan') {
7706
                    cell.isRowspan = false;
7707
                }
7708
                cell.firstCol = true;
7709
                cell.lastCol = true;
7710
                cell.firstRow = true;
7711
                cell.lastRow = true;
7712
                cell.isReal = true;
7713
            }
7714
        },
7715
7716
        removeSurplusLines: function() {
7717
            var row, cell, ridx, rmax, cidx, cmax, allRowspan;
7718
7719
            this.setTableMap();
7720
            if (this.map) {
7721
                ridx = 0;
7722
                rmax = this.map.length;
7723
                for (;ridx < rmax; ridx++) {
7724
                    row = this.map[ridx];
7725
                    allRowspan = true;
7726
                    cidx = 0;
7727
                    cmax = row.length;
7728
                    for (; cidx < cmax; cidx++) {
7729
                        cell = row[cidx];
7730
                        if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) {
7731
                            allRowspan = false;
7732
                            break;
7733
                        }
7734
                    }
7735
                    if (allRowspan) {
7736
                        cidx = 0;
7737
                        for (; cidx < cmax; cidx++) {
7738
                            this.decreaseCellSpan(row[cidx], 'rowspan');
7739
                        }
7740
                    }
7741
                }
7742
7743
                // remove rows without cells
7744
                var tableRows = this.getTableRows();
7745
                ridx = 0;
7746
                rmax = tableRows.length;
7747
                for (;ridx < rmax; ridx++) {
7748
                    row = tableRows[ridx];
7749
                    if (row.childNodes.length == 0 && (/^\s*$/.test(row.textContent || row.innerText))) {
7750
                        removeElement(row);
7751
                    }
7752
                }
7753
            }
7754
        },
7755
7756
        fillMissingCells: function() {
7757
            var r_max = 0,
7758
                c_max = 0,
7759
                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...
7760
7761
            this.setTableMap();
7762
            if (this.map) {
7763
7764
                // find maximal dimensions of broken table
7765
                r_max = this.map.length;
7766
                for (var ridx = 0; ridx < r_max; ridx++) {
7767
                    if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; }
7768
                }
7769
7770
                for (var row = 0; row < r_max; row++) {
7771
                    for (var col = 0; col < c_max; col++) {
7772
                        if (this.map[row] && !this.map[row][col]) {
7773
                            if (col > 0) {
7774
                                this.map[row][col] = new MapCell(this.createCells('td', 1));
7775
                                prevcell = this.map[row][col-1];
7776
                                if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom
7777
                                    insertAfter(this.map[row][col-1].el, this.map[row][col].el);
7778
                                }
7779
                            }
7780
                        }
7781
                    }
7782
                }
7783
            }
7784
        },
7785
7786
        rectify: function() {
7787
            if (!this.removeEmptyTable()) {
7788
                this.removeSurplusLines();
7789
                this.fillMissingCells();
7790
                return true;
7791
            } else {
7792
                return false;
7793
            }
7794
        },
7795
7796
        unmerge: function() {
7797
            if (this.rectify()) {
7798
                this.setTableMap();
7799
                this.idx = this.getMapIndex(this.cell);
7800
7801
                if (this.idx) {
7802
                    var thisCell = this.map[this.idx.row][this.idx.col],
7803
                        colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1,
7804
                        cType = thisCell.el.tagName.toLowerCase();
7805
7806
                    if (thisCell.isRowspan) {
7807
                        var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10);
7808
                        if (rowspan > 1) {
7809
                            for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){
7810
                                this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell);
7811
                            }
7812
                        }
7813
                        thisCell.el.removeAttribute('rowspan');
7814
                    }
7815
                    this.splitRowToCells(thisCell);
7816
                }
7817
            }
7818
        },
7819
7820
        // merges cells from start cell (defined in creating obj) to "to" cell
7821
        merge: function(to) {
7822
            if (this.rectify()) {
7823
                if (this.canMerge(to)) {
7824
                    var rowspan = this.idx_end.row - this.idx_start.row + 1,
7825
                        colspan = this.idx_end.col - this.idx_start.col + 1;
7826
7827
                    for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) {
7828
                        for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) {
7829
7830
                            if (row == this.idx_start.row && col == this.idx_start.col) {
7831
                                if (rowspan > 1) {
7832
                                    this.map[row][col].el.setAttribute('rowspan', rowspan);
7833
                                }
7834
                                if (colspan > 1) {
7835
                                    this.map[row][col].el.setAttribute('colspan', colspan);
7836
                                }
7837
                            } else {
7838
                                // transfer content
7839
                                if (!(/^\s*<br\/?>\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) {
7840
                                    this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML;
7841
                                }
7842
                                removeElement(this.map[row][col].el);
7843
                            }
7844
                        }
7845
                    }
7846
                    this.rectify();
7847
                } else {
7848
                    if (window.console) {
7849
                        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...
7850
                    }
7851
                }
7852
            }
7853
        },
7854
7855
        // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell)
7856
        // Cell is moved to next row (if it is real)
7857
        collapseCellToNextRow: function(cell) {
7858
            var cellIdx = this.getMapIndex(cell.el),
7859
                newRowIdx = cellIdx.row + 1,
7860
                newIdx = {'row': newRowIdx, 'col': cellIdx.col};
7861
7862
            if (newRowIdx < this.map.length) {
7863
7864
                var row = this.getRealRowEl(false, newIdx);
7865
                if (row !== null) {
7866
                    var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row);
7867
                    if (n_cidx >= 0) {
7868
                        insertAfter(this.getRowCells(row)[n_cidx], cell.el);
7869
                    } else {
7870
                        var lastCell = this.getLastNewCellOnRow(row, newRowIdx);
7871
                        if (lastCell !== null) {
7872
                            insertAfter(lastCell, cell.el);
7873
                        } else {
7874
                            row.insertBefore(cell.el, row.firstChild);
7875
                        }
7876
                    }
7877
                    if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7878
                        cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7879
                    } else {
7880
                        cell.el.removeAttribute('rowspan');
7881
                    }
7882
                }
7883
            }
7884
        },
7885
7886
        // Removes a cell when removing a row
7887
        // If is rowspan cell then decreases the rowspan
7888
        // and moves cell to next row if needed (is first cell of rowspan)
7889
        removeRowCell: function(cell) {
7890
            if (cell.isReal) {
7891
               if (cell.isRowspan) {
7892
                   this.collapseCellToNextRow(cell);
7893
               } else {
7894
                   removeElement(cell.el);
7895
               }
7896
            } else {
7897
                if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) {
7898
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1);
7899
                } else {
7900
                    cell.el.removeAttribute('rowspan');
7901
                }
7902
            }
7903
        },
7904
7905
        getRowElementsByCell: function() {
7906
            var cells = [];
7907
            this.setTableMap();
7908
            this.idx = this.getMapIndex(this.cell);
7909
            if (this.idx !== false) {
7910
                var modRow = this.map[this.idx.row];
7911
                for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7912
                    if (modRow[cidx].isReal) {
7913
                        cells.push(modRow[cidx].el);
7914
                    }
7915
                }
7916
            }
7917
            return cells;
7918
        },
7919
7920
        getColumnElementsByCell: function() {
7921
            var cells = [];
7922
            this.setTableMap();
7923
            this.idx = this.getMapIndex(this.cell);
7924
            if (this.idx !== false) {
7925
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7926
                    if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) {
7927
                        cells.push(this.map[ridx][this.idx.col].el);
7928
                    }
7929
                }
7930
            }
7931
            return cells;
7932
        },
7933
7934
        // Removes the row of selected cell
7935
        removeRow: function() {
7936
            var oldRow = api.getParentElement(this.cell, { nodeName: ["TR"] });
7937
            if (oldRow) {
7938
                this.setTableMap();
7939
                this.idx = this.getMapIndex(this.cell);
7940
                if (this.idx !== false) {
7941
                    var modRow = this.map[this.idx.row];
7942
                    for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) {
7943
                        if (!modRow[cidx].modified) {
7944
                            this.setCellAsModified(modRow[cidx]);
7945
                            this.removeRowCell(modRow[cidx]);
7946
                        }
7947
                    }
7948
                }
7949
                removeElement(oldRow);
7950
            }
7951
        },
7952
7953
        removeColCell: function(cell) {
7954
            if (cell.isColspan) {
7955
                if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) {
7956
                    cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1);
7957
                } else {
7958
                    cell.el.removeAttribute('colspan');
7959
                }
7960
            } else if (cell.isReal) {
7961
                removeElement(cell.el);
7962
            }
7963
        },
7964
7965
        removeColumn: function() {
7966
            this.setTableMap();
7967
            this.idx = this.getMapIndex(this.cell);
7968
            if (this.idx !== false) {
7969
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) {
7970
                    if (!this.map[ridx][this.idx.col].modified) {
7971
                        this.setCellAsModified(this.map[ridx][this.idx.col]);
7972
                        this.removeColCell(this.map[ridx][this.idx.col]);
7973
                    }
7974
                }
7975
            }
7976
        },
7977
7978
        // removes row or column by selected cell element
7979
        remove: function(what) {
7980
            if (this.rectify()) {
7981
                switch (what) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
7982
                    case 'row':
7983
                        this.removeRow();
7984
                    break;
7985
                    case 'column':
7986
                        this.removeColumn();
7987
                    break;
7988
                }
7989
                this.rectify();
7990
            }
7991
        },
7992
7993
        addRow: function(where) {
7994
            var doc = this.table.ownerDocument;
7995
7996
            this.setTableMap();
7997
            this.idx = this.getMapIndex(this.cell);
7998
            if (where == "below" && api.getAttribute(this.cell, 'rowspan')) {
7999
                this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1;
8000
            }
8001
8002
            if (this.idx !== false) {
8003
                var modRow = this.map[this.idx.row],
8004
                    newRow = doc.createElement('tr');
8005
8006
                for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) {
8007
                    if (!modRow[ridx].modified) {
8008
                        this.setCellAsModified(modRow[ridx]);
8009
                        this.addRowCell(modRow[ridx], newRow, where);
8010
                    }
8011
                }
8012
8013
                switch (where) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
8014
                    case 'below':
8015
                        insertAfter(this.getRealRowEl(true), newRow);
8016
                    break;
8017
                    case 'above':
8018
                        var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { nodeName: ["TR"] });
8019
                        if (cr) {
8020
                            cr.parentNode.insertBefore(newRow, cr);
8021
                        }
8022
                    break;
8023
                }
8024
            }
8025
        },
8026
8027
        addRowCell: function(cell, row, where) {
8028
            var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null;
8029
            if (cell.isReal) {
8030
                if (where != 'above' && cell.isRowspan) {
8031
                    cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1);
8032
                } else {
8033
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
8034
                }
8035
            } else {
8036
                if (where != 'above' && cell.isRowspan && cell.lastRow) {
8037
                    row.appendChild(this.createCells('td', 1, colSpanAttr));
8038
                } 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...
8039
                    cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1);
8040
                }
8041
            }
8042
        },
8043
8044
        add: function(where) {
8045
            if (this.rectify()) {
8046
                if (where == 'below' || where == 'above') {
8047
                    this.addRow(where);
8048
                }
8049
                if (where == 'before' || where == 'after') {
8050
                    this.addColumn(where);
8051
                }
8052
            }
8053
        },
8054
8055
        addColCell: function (cell, ridx, where) {
8056
            var doAdd,
8057
                cType = cell.el.tagName.toLowerCase();
8058
8059
            // defines add cell vs expand cell conditions
8060
            // true means add
8061
            switch (where) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
8062
                case "before":
8063
                    doAdd = (!cell.isColspan || cell.firstCol);
8064
                break;
8065
                case "after":
8066
                    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...
8067
                break;
8068
            }
8069
8070
            if (doAdd){
8071
                // adds a cell before or after current cell element
8072
                switch (where) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
8073
                    case "before":
8074
                        cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el);
8075
                    break;
8076
                    case "after":
8077
                        insertAfter(cell.el, this.createCells(cType, 1));
8078
                    break;
8079
                }
8080
8081
                // handles if cell has rowspan
8082
                if (cell.isRowspan) {
8083
                    this.handleCellAddWithRowspan(cell, ridx+1, where);
8084
                }
8085
8086
            } else {
8087
                // expands cell
8088
                cell.el.setAttribute('colspan',  parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1);
8089
            }
8090
        },
8091
8092
        addColumn: function(where) {
8093
            var row, modCell;
8094
8095
            this.setTableMap();
8096
            this.idx = this.getMapIndex(this.cell);
8097
            if (where == "after" && api.getAttribute(this.cell, 'colspan')) {
8098
              this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1;
8099
            }
8100
8101
            if (this.idx !== false) {
8102
                for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) {
8103
                    row = this.map[ridx];
8104
                    if (row[this.idx.col]) {
8105
                        modCell = row[this.idx.col];
8106
                        if (!modCell.modified) {
8107
                            this.setCellAsModified(modCell);
8108
                            this.addColCell(modCell, ridx , where);
8109
                        }
8110
                    }
8111
                }
8112
            }
8113
        },
8114
8115
        handleCellAddWithRowspan: function (cell, ridx, where) {
8116
            var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1,
8117
                crow = api.getParentElement(cell.el, { nodeName: ["TR"] }),
8118
                cType = cell.el.tagName.toLowerCase(),
8119
                cidx, temp_r_cells,
8120
                doc = this.table.ownerDocument,
8121
                nrow;
8122
8123
            for (var i = 0; i < addRowsNr; i++) {
8124
                cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i));
8125
                crow = nextNode(crow, 'tr');
8126
                if (crow) {
8127
                    if (cidx > 0) {
8128
                        switch (where) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
8129
                            case "before":
8130
                                temp_r_cells = this.getRowCells(crow);
8131
                                if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) {
8132
                                     insertAfter(temp_r_cells[cidx], this.createCells(cType, 1));
8133
                                } else {
8134
                                    temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]);
8135
                                }
8136
8137
                            break;
8138
                            case "after":
8139
                                insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1));
8140
                            break;
8141
                        }
8142
                    } else {
8143
                        crow.insertBefore(this.createCells(cType, 1), crow.firstChild);
8144
                    }
8145
                } else {
8146
                    nrow = doc.createElement('tr');
8147
                    nrow.appendChild(this.createCells(cType, 1));
8148
                    this.table.appendChild(nrow);
8149
                }
8150
            }
8151
        }
8152
    };
8153
8154
    api.table = {
8155
        getCellsBetween: function(cell1, cell2) {
8156
            var c1 = new TableModifyerByCell(cell1);
8157
            return c1.getMapElsTo(cell2);
8158
        },
8159
8160
        addCells: function(cell, where) {
8161
            var c = new TableModifyerByCell(cell);
8162
            c.add(where);
8163
        },
8164
8165
        removeCells: function(cell, what) {
8166
            var c = new TableModifyerByCell(cell);
8167
            c.remove(what);
8168
        },
8169
8170
        mergeCellsBetween: function(cell1, cell2) {
8171
            var c1 = new TableModifyerByCell(cell1);
8172
            c1.merge(cell2);
8173
        },
8174
8175
        unmergeCell: function(cell) {
8176
            var c = new TableModifyerByCell(cell);
8177
            c.unmerge();
8178
        },
8179
8180
        orderSelectionEnds: function(cell, cell2) {
8181
            var c = new TableModifyerByCell(cell);
8182
            return c.orderSelectionEnds(cell2);
8183
        },
8184
8185
        indexOf: function(cell) {
8186
            var c = new TableModifyerByCell(cell);
8187
            c.setTableMap();
8188
            return c.getMapIndex(cell);
8189
        },
8190
8191
        findCell: function(table, idx) {
8192
            var c = new TableModifyerByCell(null, table);
8193
            return c.getElementAtIndex(idx);
8194
        },
8195
8196
        findRowByCell: function(cell) {
8197
            var c = new TableModifyerByCell(cell);
8198
            return c.getRowElementsByCell();
8199
        },
8200
8201
        findColumnByCell: function(cell) {
8202
            var c = new TableModifyerByCell(cell);
8203
            return c.getColumnElementsByCell();
8204
        },
8205
8206
        canMerge: function(cell1, cell2) {
8207
            var c = new TableModifyerByCell(cell1);
8208
            return c.canMerge(cell2);
8209
        }
8210
    };
8211
8212
8213
8214
})(wysihtml5);
8215
;// does a selector query on element or array of elements
8216
8217
wysihtml5.dom.query = function(elements, query) {
8218
    var ret = [],
8219
        q;
8220
8221
    if (elements.nodeType) {
8222
        elements = [elements];
8223
    }
8224
8225
    for (var e = 0, len = elements.length; e < len; e++) {
8226
        q = elements[e].querySelectorAll(query);
8227
        if (q) {
8228
            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...
8229
        }
8230
    }
8231
    return ret;
8232
};
8233
;wysihtml5.dom.compareDocumentPosition = (function() {
8234
  var documentElement = document.documentElement;
8235
  if (documentElement.compareDocumentPosition) {
8236
    return function(container, element) {
8237
      return container.compareDocumentPosition(element);
8238
    };
8239
  } else {
8240
    return function( container, element ) {
8241
      // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
8242
      var thisOwner, otherOwner;
8243
8244
      if( container.nodeType === 9) // Node.DOCUMENT_NODE
8245
        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...
8246
      else
8247
        thisOwner = container.ownerDocument;
8248
8249
      if( element.nodeType === 9) // Node.DOCUMENT_NODE
8250
        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...
8251
      else
8252
        otherOwner = element.ownerDocument;
8253
8254
      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...
8255
      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...
8256
      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...
8257
      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...
8258
8259
      // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
8260
      if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml5.lang.array(container.childNodes).indexOf( element ) !== -1)
8261
        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...
8262
8263
      if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml5.lang.array(element.childNodes).indexOf( container ) !== -1)
8264
        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...
8265
8266
      var point = container;
8267
      var parents = [ ];
8268
      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...
8269
      while( point ) {
8270
        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...
8271
        parents.push( point );
8272
        point = point.parentNode;
8273
      }
8274
      point = element;
8275
      previous = null;
8276
      while( point ) {
8277
        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...
8278
        var location_index = wysihtml5.lang.array(parents).indexOf( point );
8279
        if( location_index !== -1) {
8280
         var smallest_common_ancestor = parents[ location_index ];
8281
         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] );
8282
         var other_index = wysihtml5.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
8283
         if( this_index > other_index ) {
8284
               return 2; //Node.DOCUMENT_POSITION_PRECEDING;
8285
         }
8286
         else {
8287
           return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
8288
         }
8289
        }
8290
        previous = point;
8291
        point = point.parentNode;
8292
      }
8293
      return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
8294
    };
8295
  }
8296
})();
8297
;wysihtml5.dom.unwrap = function(node) {
8298
  if (node.parentNode) {
8299
    while (node.lastChild) {
8300
      wysihtml5.dom.insert(node.lastChild).after(node);
8301
    }
8302
    node.parentNode.removeChild(node);
8303
  }
8304
};;/* 
8305
 * Methods for fetching pasted html before it gets inserted into content
8306
**/
8307
8308
/* Modern event.clipboardData driven approach.
8309
 * Advantage is that it does not have to loose selection or modify dom to catch the data. 
8310
 * IE does not support though.
8311
**/
8312
wysihtml5.dom.getPastedHtml = function(event) {
8313
  var html;
8314
  if (event.clipboardData) {
8315
    if (wysihtml5.lang.array(event.clipboardData.types).contains('text/html')) {
8316
      html = event.clipboardData.getData('text/html');
8317
    } else if (wysihtml5.lang.array(event.clipboardData.types).contains('text/plain')) {
8318
      html = wysihtml5.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
8319
    }
8320
  }
8321
  return html;
0 ignored issues
show
Bug introduced by
The variable html seems to not be initialized for all possible execution paths.
Loading history...
8322
};
8323
8324
/* Older temprorary contenteditable as paste source catcher method for fallbacks */
8325
wysihtml5.dom.getPastedHtmlWithDiv = function (composer, f) {
8326
  var selBookmark = composer.selection.getBookmark(),
8327
      doc = composer.element.ownerDocument,
8328
      cleanerDiv = doc.createElement('DIV');
8329
  
8330
  doc.body.appendChild(cleanerDiv);
8331
8332
  cleanerDiv.style.width = "1px";
8333
  cleanerDiv.style.height = "1px";
8334
  cleanerDiv.style.overflow = "hidden";
8335
8336
  cleanerDiv.setAttribute('contenteditable', 'true');
8337
  cleanerDiv.focus();
8338
8339
  setTimeout(function () {
8340
    composer.selection.setBookmark(selBookmark);
8341
    f(cleanerDiv.innerHTML);
8342
    cleanerDiv.parentNode.removeChild(cleanerDiv);
8343
  }, 0);
8344
};;/**
8345
 * Fix most common html formatting misbehaviors of browsers implementation when inserting
8346
 * content via copy & paste contentEditable
8347
 *
8348
 * @author Christopher Blum
8349
 */
8350
wysihtml5.quirks.cleanPastedHTML = (function() {
8351
8352
  var styleToRegex = function (styleStr) {
8353
    var trimmedStr = wysihtml5.lang.string(styleStr).trim(),
8354
        escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
8355
8356
    return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
8357
  };
8358
8359
  var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
8360
    var newRules = wysihtml5.lang.object(rules).clone(true),
8361
        tag, style;
8362
8363
    for (tag in newRules.tags) {
8364
8365
      if (newRules.tags.hasOwnProperty(tag)) {
8366
        if (newRules.tags[tag].keep_styles) {
8367
          for (style in newRules.tags[tag].keep_styles) {
8368
            if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
8369
              if (exceptStyles[style]) {
8370
                newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
8371
              }
8372
            }
8373
          }
8374
        }
8375
      }
8376
    }
8377
8378
    return newRules;
8379
  };
8380
8381
  var pickRuleset = function(ruleset, html) {
8382
    var pickedSet, defaultSet;
0 ignored issues
show
Unused Code introduced by
The variable pickedSet seems to be never used. Consider removing it.
Loading history...
8383
8384
    if (!ruleset) {
8385
      return null;
8386
    }
8387
8388
    for (var i = 0, max = ruleset.length; i < max; i++) {
8389
      if (!ruleset[i].condition) {
8390
        defaultSet = ruleset[i].set;
8391
      }
8392
      if (ruleset[i].condition && ruleset[i].condition.test(html)) {
8393
        return ruleset[i].set;
8394
      }
8395
    }
8396
8397
    return defaultSet;
0 ignored issues
show
Bug introduced by
The variable defaultSet seems to not be initialized for all possible execution paths.
Loading history...
8398
  };
8399
8400
  return function(html, options) {
8401
    var exceptStyles = {
8402
          'color': wysihtml5.dom.getStyle("color").from(options.referenceNode),
8403
          'fontSize': wysihtml5.dom.getStyle("font-size").from(options.referenceNode)
8404
        },
8405
        rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
8406
        newHtml;
8407
8408
    newHtml = wysihtml5.dom.parse(html, {
8409
      "rules": rules,
8410
      "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
8411
      "context": options.referenceNode.ownerDocument,
8412
      "uneditableClass": options.uneditableClass,
8413
      "clearInternals" : true, // don't paste temprorary selection and other markings
8414
      "unjoinNbsps" : true
8415
    });
8416
8417
    return newHtml;
8418
  };
8419
8420
})();;/**
8421
 * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
8422
 *
8423
 * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
0 ignored issues
show
Documentation introduced by
The parameter contentEditableElement does not exist. Did you maybe forget to remove this comment?
Loading history...
8424
 * @exaple
8425
 *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
8426
 */
8427
wysihtml5.quirks.ensureProperClearing = (function() {
8428
  var clearIfNecessary = function() {
8429
    var element = this;
8430
    setTimeout(function() {
8431
      var innerHTML = element.innerHTML.toLowerCase();
8432
      if (innerHTML == "<p>&nbsp;</p>" ||
8433
          innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
8434
        element.innerHTML = "";
8435
      }
8436
    }, 0);
8437
  };
8438
8439
  return function(composer) {
8440
    wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
8441
  };
8442
})();
8443
;// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
8444
//
8445
// In Firefox this:
8446
//      var d = document.createElement("div");
8447
//      d.innerHTML ='<a href="~"></a>';
8448
//      d.innerHTML;
8449
// will result in:
8450
//      <a href="%7E"></a>
8451
// which is wrong
8452
(function(wysihtml5) {
8453
  var TILDE_ESCAPED = "%7E";
8454
  wysihtml5.quirks.getCorrectInnerHTML = function(element) {
8455
    var innerHTML = element.innerHTML;
8456
    if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
8457
      return innerHTML;
8458
    }
8459
8460
    var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
8461
        url,
8462
        urlToSearch,
8463
        length,
8464
        i;
8465
    for (i=0, length=elementsWithTilde.length; i<length; i++) {
8466
      url         = elementsWithTilde[i].href || elementsWithTilde[i].src;
8467
      urlToSearch = wysihtml5.lang.string(url).replace("~").by(TILDE_ESCAPED);
8468
      innerHTML   = wysihtml5.lang.string(innerHTML).replace(urlToSearch).by(url);
8469
    }
8470
    return innerHTML;
8471
  };
8472
})(wysihtml5);
8473
;/**
8474
 * Force rerendering of a given element
8475
 * Needed to fix display misbehaviors of IE
8476
 *
8477
 * @param {Element} element The element object which needs to be rerendered
0 ignored issues
show
Documentation introduced by
The parameter element does not exist. Did you maybe forget to remove this comment?
Loading history...
8478
 * @example
8479
 *    wysihtml5.quirks.redraw(document.body);
8480
 */
8481
(function(wysihtml5) {
8482
  var CLASS_NAME = "wysihtml5-quirks-redraw";
8483
8484
  wysihtml5.quirks.redraw = function(element) {
8485
    wysihtml5.dom.addClass(element, CLASS_NAME);
8486
    wysihtml5.dom.removeClass(element, CLASS_NAME);
8487
8488
    // Following hack is needed for firefox to make sure that image resize handles are properly removed
8489
    try {
8490
      var doc = element.ownerDocument;
8491
      doc.execCommand("italic", false, null);
8492
      doc.execCommand("italic", false, null);
8493
    } 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...
8494
  };
8495
})(wysihtml5);
8496
;wysihtml5.quirks.tableCellsSelection = function(editable, editor) {
8497
8498
    var dom = wysihtml5.dom,
8499
        select = {
8500
            table: null,
8501
            start: null,
8502
            end: null,
8503
            cells: null,
8504
            select: selectCells
8505
        },
8506
        selection_class = "wysiwyg-tmp-selected-cell",
8507
        moveHandler = null,
8508
        upHandler = null;
8509
8510
    function init () {
8511
8512
        dom.observe(editable, "mousedown", function(event) {
8513
          var target = wysihtml5.dom.getParentElement(event.target, { nodeName: ["TD", "TH"] });
8514
          if (target) {
8515
              handleSelectionMousedown(target);
8516
          }
8517
        });
8518
8519
        return select;
8520
    }
8521
8522
    function handleSelectionMousedown (target) {
8523
      select.start = target;
8524
      select.end = target;
8525
      select.cells = [target];
8526
      select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8527
8528
      if (select.table) {
8529
        removeCellSelections();
8530
        dom.addClass(target, selection_class);
8531
        moveHandler = dom.observe(editable, "mousemove", handleMouseMove);
8532
        upHandler = dom.observe(editable, "mouseup", handleMouseUp);
8533
        editor.fire("tableselectstart").fire("tableselectstart:composer");
8534
      }
8535
    }
8536
8537
    // remove all selection classes
8538
    function removeCellSelections () {
8539
        if (editable) {
8540
            var selectedCells = editable.querySelectorAll('.' + selection_class);
8541
            if (selectedCells.length > 0) {
8542
              for (var i = 0; i < selectedCells.length; i++) {
8543
                  dom.removeClass(selectedCells[i], selection_class);
8544
              }
8545
            }
8546
        }
8547
    }
8548
8549
    function addSelections (cells) {
8550
      for (var i = 0; i < cells.length; i++) {
8551
        dom.addClass(cells[i], selection_class);
8552
      }
8553
    }
8554
8555
    function handleMouseMove (event) {
8556
      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...
8557
          cell = dom.getParentElement(event.target, { nodeName: ["TD","TH"] }),
8558
          oldEnd;
8559
8560
      if (cell && select.table && select.start) {
8561
        curTable =  dom.getParentElement(cell, { nodeName: ["TABLE"] });
8562
        if (curTable && curTable === select.table) {
8563
          removeCellSelections();
8564
          oldEnd = select.end;
8565
          select.end = cell;
8566
          select.cells = dom.table.getCellsBetween(select.start, cell);
8567
          if (select.cells.length > 1) {
8568
            editor.composer.selection.deselect();
8569
          }
8570
          addSelections(select.cells);
8571
          if (select.end !== oldEnd) {
8572
            editor.fire("tableselectchange").fire("tableselectchange:composer");
8573
          }
8574
        }
8575
      }
8576
    }
8577
8578
    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...
8579
      moveHandler.stop();
8580
      upHandler.stop();
8581
      editor.fire("tableselect").fire("tableselect:composer");
8582
      setTimeout(function() {
8583
        bindSideclick();
8584
      },0);
8585
    }
8586
8587
    function bindSideclick () {
8588
        var sideClickHandler = dom.observe(editable.ownerDocument, "click", function(event) {
8589
          sideClickHandler.stop();
8590
          if (dom.getParentElement(event.target, { nodeName: ["TABLE"] }) != select.table) {
8591
              removeCellSelections();
8592
              select.table = null;
8593
              select.start = null;
8594
              select.end = null;
8595
              editor.fire("tableunselect").fire("tableunselect:composer");
8596
          }
8597
        });
8598
    }
8599
8600
    function selectCells (start, end) {
8601
        select.start = start;
8602
        select.end = end;
8603
        select.table = dom.getParentElement(select.start, { nodeName: ["TABLE"] });
8604
        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...
8605
        addSelections(selectedCells);
8606
        bindSideclick();
8607
        editor.fire("tableselect").fire("tableselect:composer");
8608
    }
8609
8610
    return init();
8611
8612
};
8613
;(function(wysihtml5) {
8614
  var RGBA_REGEX     = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
8615
      RGB_REGEX      = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
8616
      HEX6_REGEX     = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
8617
      HEX3_REGEX     = /^#([0-9a-f])([0-9a-f])([0-9a-f])/i;
8618
8619
  var param_REGX = function (p) {
8620
    return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+" , "gi");
8621
  };
8622
8623
  wysihtml5.quirks.styleParser = {
8624
8625
    parseColor: function(stylesStr, paramName) {
8626
      var paramRegex = param_REGX(paramName),
8627
          params = stylesStr.match(paramRegex),
8628
          radix = 10,
8629
          str, colorMatch;
8630
8631
      if (params) {
8632
        for (var i = params.length; i--;) {
8633
          params[i] = wysihtml5.lang.string(params[i].split(':')[1]).trim();
8634
        }
8635
        str = params[params.length-1];
8636
8637
        if (RGBA_REGEX.test(str)) {
8638
          colorMatch = str.match(RGBA_REGEX);
8639
        } else if (RGB_REGEX.test(str)) {
8640
          colorMatch = str.match(RGB_REGEX);
8641
        } else if (HEX6_REGEX.test(str)) {
8642
          colorMatch = str.match(HEX6_REGEX);
8643
          radix = 16;
8644
        } else if (HEX3_REGEX.test(str)) {
8645
          colorMatch = str.match(HEX3_REGEX);
8646
          colorMatch.shift();
8647
          colorMatch.push(1);
8648
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8649
            return (idx < 3) ? (parseInt(d, 16) * 16) + parseInt(d, 16): parseFloat(d);
8650
          });
8651
        }
8652
8653
        if (colorMatch) {
8654
          colorMatch.shift();
8655
          if (!colorMatch[3]) {
8656
            colorMatch.push(1);
8657
          }
8658
          return wysihtml5.lang.array(colorMatch).map(function(d, idx) {
8659
            return (idx < 3) ? parseInt(d, radix): parseFloat(d);
8660
          });
8661
        }
8662
      }
8663
      return false;
8664
    },
8665
8666
    unparseColor: function(val, props) {
8667
      if (props) {
8668
        if (props == "hex") {
8669
          return (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8670
        } else if (props == "hash") {
8671
          return "#" + (val[0].toString(16).toUpperCase()) + (val[1].toString(16).toUpperCase()) + (val[2].toString(16).toUpperCase());
8672
        } else if (props == "rgb") {
8673
          return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8674
        } else if (props == "rgba") {
8675
          return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8676
        } else if (props == "csv") {
8677
          return  val[0] + "," + val[1] + "," + val[2] + "," + val[3];
8678
        }
8679
      }
8680
8681
      if (val[3] && val[3] !== 1) {
8682
        return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
8683
      } else {
8684
        return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
8685
      }
8686
    },
8687
8688
    parseFontSize: function(stylesStr) {
8689
      var params = stylesStr.match(param_REGX('font-size'));
8690
      if (params) {
8691
        return wysihtml5.lang.string(params[params.length - 1].split(':')[1]).trim();
8692
      }
8693
      return false;
8694
    }
8695
  };
8696
8697
})(wysihtml5);
8698
;/**
8699
 * Selection API
8700
 *
8701
 * @example
8702
 *    var selection = new wysihtml5.Selection(editor);
8703
 */
8704
(function(wysihtml5) {
8705
  var dom = wysihtml5.dom;
8706
8707
  function _getCumulativeOffsetTop(element) {
8708
    var top = 0;
8709
    if (element.parentNode) {
8710
      do {
8711
        top += element.offsetTop || 0;
8712
        element = element.offsetParent;
8713
      } while (element);
8714
    }
8715
    return top;
8716
  }
8717
8718
  // Provides the depth of ``descendant`` relative to ``ancestor``
8719
  function getDepth(ancestor, descendant) {
8720
      var ret = 0;
8721
      while (descendant !== ancestor) {
8722
          ret++;
8723
          descendant = descendant.parentNode;
8724
          if (!descendant)
8725
              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...
8726
      }
8727
      return ret;
8728
  }
8729
8730
  // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
8731
  // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
8732
  function expandRangeToSurround(range) {
8733
      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...
8734
8735
      var common = range.commonAncestorContainer,
8736
          start_depth = getDepth(common, range.startContainer),
8737
          end_depth = getDepth(common, range.endContainer);
8738
8739
      while(!range.canSurroundContents()) {
8740
        // 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.
8741
        if (start_depth > end_depth) {
8742
            range.setStartBefore(range.startContainer);
8743
            start_depth = getDepth(common, range.startContainer);
8744
        }
8745
        else {
8746
            range.setEndAfter(range.endContainer);
8747
            end_depth = getDepth(common, range.endContainer);
8748
        }
8749
      }
8750
  }
8751
8752
  wysihtml5.Selection = Base.extend(
8753
    /** @scope wysihtml5.Selection.prototype */ {
8754
    constructor: function(editor, contain, unselectableClass) {
8755
      // Make sure that our external range library is initialized
8756
      window.rangy.init();
8757
8758
      this.editor   = editor;
8759
      this.composer = editor.composer;
8760
      this.doc      = this.composer.doc;
8761
      this.contain = contain;
8762
      this.unselectableClass = unselectableClass || false;
8763
    },
8764
8765
    /**
8766
     * Get the current selection as a bookmark to be able to later restore it
8767
     *
8768
     * @return {Object} An object that represents the current selection
8769
     */
8770
    getBookmark: function() {
8771
      var range = this.getRange();
8772
      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...
8773
      return range && range.cloneRange();
8774
    },
8775
8776
    /**
8777
     * Restore a selection retrieved via wysihtml5.Selection.prototype.getBookmark
8778
     *
8779
     * @param {Object} bookmark An object that represents the current selection
8780
     */
8781
    setBookmark: function(bookmark) {
8782
      if (!bookmark) {
8783
        return;
8784
      }
8785
8786
      this.setSelection(bookmark);
8787
    },
8788
8789
    /**
8790
     * Set the caret in front of the given node
8791
     *
8792
     * @param {Object} node The element or text node where to position the caret in front of
8793
     * @example
8794
     *    selection.setBefore(myElement);
8795
     */
8796
    setBefore: function(node) {
8797
      var range = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
8798
      range.setStartBefore(node);
8799
      range.setEndBefore(node);
8800
      return this.setSelection(range);
8801
    },
8802
8803
    /**
8804
     * Set the caret after the given node
8805
     *
8806
     * @param {Object} node The element or text node where to position the caret in front of
8807
     * @example
8808
     *    selection.setBefore(myElement);
8809
     */
8810
    setAfter: function(node) {
8811
      var range = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
8812
8813
      range.setStartAfter(node);
8814
      range.setEndAfter(node);
8815
      return this.setSelection(range);
8816
    },
8817
8818
    /**
8819
     * Ability to select/mark nodes
8820
     *
8821
     * @param {Element} node The node/element to select
8822
     * @example
8823
     *    selection.selectNode(document.getElementById("my-image"));
8824
     */
8825
    selectNode: function(node, avoidInvisibleSpace) {
8826
      var range           = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
8827
          isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
8828
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
8829
          content         = isElement ? node.innerHTML : node.data,
8830
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE),
8831
          displayStyle    = dom.getStyle("display").from(node),
8832
          isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
8833
8834
      if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
8835
        // Make sure that caret is visible in node by inserting a zero width no breaking space
8836
        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
8837
      }
8838
8839
      if (canHaveHTML) {
8840
        range.selectNodeContents(node);
8841
      } else {
8842
        range.selectNode(node);
8843
      }
8844
8845
      if (canHaveHTML && isEmpty && isElement) {
8846
        range.collapse(isBlockElement);
8847
      } else if (canHaveHTML && isEmpty) {
8848
        range.setStartAfter(node);
8849
        range.setEndAfter(node);
8850
      }
8851
8852
      this.setSelection(range);
8853
    },
8854
8855
    /**
8856
     * Get the node which contains the selection
8857
     *
8858
     * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
8859
     * @return {Object} The node that contains the caret
8860
     * @example
8861
     *    var nodeThatContainsCaret = selection.getSelectedNode();
8862
     */
8863
    getSelectedNode: function(controlRange) {
8864
      var selection,
8865
          range;
8866
8867
      if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
8868
        range = this.doc.selection.createRange();
8869
        if (range && range.length) {
8870
          return range.item(0);
8871
        }
8872
      }
8873
8874
      selection = this.getSelection(this.doc);
8875
      if (selection.focusNode === selection.anchorNode) {
8876
        return selection.focusNode;
8877
      } else {
8878
        range = this.getRange(this.doc);
8879
        return range ? range.commonAncestorContainer : this.doc.body;
8880
      }
8881
    },
8882
8883
    fixSelBorders: function() {
8884
      var range = this.getRange();
8885
      expandRangeToSurround(range);
8886
      this.setSelection(range);
8887
    },
8888
8889
    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...
8890
      var selection,
0 ignored issues
show
Unused Code introduced by
The variable selection seems to be never used. Consider removing it.
Loading history...
8891
          ranges = this.getOwnRanges(),
8892
          ownNodes = [];
8893
8894
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8895
          ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
8896
      }
8897
      return ownNodes;
8898
    },
8899
8900
    findNodesInSelection: function(nodeTypes) {
8901
      var ranges = this.getOwnRanges(),
8902
          nodes = [], curNodes;
8903
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
8904
        curNodes = ranges[i].getNodes([1], function(node) {
8905
            return wysihtml5.lang.array(nodeTypes).contains(node.nodeName);
8906
        });
8907
        nodes = nodes.concat(curNodes);
8908
      }
8909
      return nodes;
8910
    },
8911
8912
    containsUneditable: function() {
8913
      var uneditables = this.getOwnUneditables(),
8914
          selection = this.getSelection();
8915
8916
      for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
8917
        if (selection.containsNode(uneditables[i])) {
8918
          return true;
8919
        }
8920
      }
8921
8922
      return false;
8923
    },
8924
8925
    deleteContents: function()  {
8926
      var ranges = this.getOwnRanges();
8927
      for (var i = ranges.length; i--;) {
8928
        ranges[i].deleteContents();
8929
      }
8930
      this.setSelection(ranges[0]);
8931
    },
8932
8933
    getPreviousNode: function(node, ignoreEmpty) {
8934
      if (!node) {
8935
        var selection = this.getSelection();
8936
        node = selection.anchorNode;
8937
      }
8938
8939
      if (node === this.contain) {
8940
          return false;
8941
      }
8942
8943
      var ret = node.previousSibling,
8944
          parent;
8945
8946
      if (ret === this.contain) {
8947
          return false;
8948
      }
8949
8950
      if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
8951
         // do not count comments and other node types
8952
         ret = this.getPreviousNode(ret, ignoreEmpty);
8953
      } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
8954
        // do not count empty textnodes as previus nodes
8955
        ret = this.getPreviousNode(ret, ignoreEmpty);
8956
      } else if (ignoreEmpty && ret && ret.nodeType === 1 && !wysihtml5.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && (/^[\s]*$/).test(ret.innerHTML)) {
8957
        // Do not count empty nodes if param set.
8958
        // Contenteditable tends to bypass and delete these silently when deleting with caret
8959
        ret = this.getPreviousNode(ret, ignoreEmpty);
8960
      } else if (!ret && node !== this.contain) {
8961
        parent = node.parentNode;
8962
        if (parent !== this.contain) {
8963
            ret = this.getPreviousNode(parent, ignoreEmpty);
8964
        }
8965
      }
8966
8967
      return (ret !== this.contain) ? ret : false;
8968
    },
8969
8970
    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...
8971
      var nodes = this.getSelectedOwnNodes(),
8972
          curEl, parents = [];
8973
8974
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
8975
        curEl = (nodes[i].nodeName &&  nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml5.dom.getParentElement(nodes[i], { nodeName: ['LI']}, false, this.contain);
8976
        if (curEl) {
8977
          parents.push(curEl);
8978
        }
8979
      }
8980
      return (parents.length) ? parents : null;
8981
    },
8982
8983
    getRangeToNodeEnd: function() {
8984
      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...
8985
        var range = this.getRange(),
8986
            sNode = range.startContainer,
8987
            pos = range.startOffset,
8988
            lastR = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
8989
8990
        lastR.selectNodeContents(sNode);
8991
        lastR.setStart(sNode, pos);
8992
        return lastR;
8993
      }
8994
    },
8995
8996
    caretIsLastInSelection: function() {
8997
      var r = rangy.createRange(this.doc),
0 ignored issues
show
Unused Code introduced by
The variable r seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
8998
          s = this.getSelection(),
0 ignored issues
show
Unused Code introduced by
The variable s seems to be never used. Consider removing it.
Loading history...
8999
          endc = this.getRangeToNodeEnd().cloneContents(),
9000
          endtxt = endc.textContent;
9001
9002
      return (/^\s*$/).test(endtxt);
9003
    },
9004
9005
    caretIsFirstInSelection: function() {
9006
      var r = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9007
          s = this.getSelection(),
9008
          range = this.getRange(),
9009
          startNode = range.startContainer;
9010
      
9011
      if (startNode.nodeType === wysihtml5.TEXT_NODE) {
9012
        return this.isCollapsed() && (startNode.nodeType === wysihtml5.TEXT_NODE && (/^\s*$/).test(startNode.data.substr(0,range.startOffset)));
9013
      } else {
9014
        r.selectNodeContents(this.getRange().commonAncestorContainer);
9015
        r.collapse(true);
9016
        return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
9017
      }
9018
    },
9019
9020
    caretIsInTheBeginnig: function(ofNode) {
9021
        var selection = this.getSelection(),
9022
            node = selection.anchorNode,
9023
            offset = selection.anchorOffset;
9024
        if (ofNode) {
9025
          return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml5.dom.getParentElement(node.parentNode, { nodeName: ofNode }, 1)));
9026
        } else {
9027
          return (offset === 0 && !this.getPreviousNode(node, true));
9028
        }
9029
    },
9030
9031
    caretIsBeforeUneditable: function() {
9032
      var selection = this.getSelection(),
9033
          node = selection.anchorNode,
9034
          offset = selection.anchorOffset;
9035
9036
      if (offset === 0) {
9037
        var prevNode = this.getPreviousNode(node, true);
9038
        if (prevNode) {
9039
          var uneditables = this.getOwnUneditables();
9040
          for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
9041
            if (prevNode === uneditables[i]) {
9042
              return uneditables[i];
9043
            }
9044
          }
9045
        }
9046
      }
9047
      return false;
9048
    },
9049
9050
    // TODO: Figure out a method from following 2 that would work universally
9051
    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...
9052
      var win = this.doc.defaultView || this.doc.parentWindow,
9053
          sel = rangy.saveSelection(win);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9054
9055
      if (!sel) {
9056
        method();
9057
      } else {
9058
        try {
9059
          method();
9060
        } catch(e) {
9061
          setTimeout(function() { throw e; }, 0);
9062
        }
9063
      }
9064
      rangy.restoreSelection(sel);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9065
    },
9066
9067
    // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
9068
    executeAndRestore: function(method, restoreScrollPosition) {
9069
      var body                  = this.doc.body,
9070
          oldScrollTop          = restoreScrollPosition && body.scrollTop,
9071
          oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
9072
          className             = "_wysihtml5-temp-placeholder",
9073
          placeholderHtml       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
9074
          range                 = this.getRange(true),
9075
          caretPlaceholder,
9076
          newCaretPlaceholder,
9077
          nextSibling, prevSibling,
9078
          node, node2, range2,
9079
          newRange;
9080
9081
      // Nothing selected, execute and say goodbye
9082
      if (!range) {
9083
        method(body, body);
9084
        return;
9085
      }
9086
9087
      if (!range.collapsed) {
9088
        range2 = range.cloneRange();
9089
        node2 = range2.createContextualFragment(placeholderHtml);
9090
        range2.collapse(false);
9091
        range2.insertNode(node2);
9092
        range2.detach();
9093
      }
9094
9095
      node = range.createContextualFragment(placeholderHtml);
9096
      range.insertNode(node);
9097
9098
      if (node2) {
9099
        caretPlaceholder = this.contain.querySelectorAll("." + className);
9100
        range.setStartBefore(caretPlaceholder[0]);
9101
        range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
9102
      }
9103
      this.setSelection(range);
9104
9105
      // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
9106
      try {
9107
        method(range.startContainer, range.endContainer);
9108
      } catch(e) {
9109
        setTimeout(function() { throw e; }, 0);
9110
      }
9111
      caretPlaceholder = this.contain.querySelectorAll("." + className);
9112
      if (caretPlaceholder && caretPlaceholder.length) {
9113
        newRange = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9114
        nextSibling = caretPlaceholder[0].nextSibling;
9115
        if (caretPlaceholder.length > 1) {
9116
          prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
9117
        }
9118
        if (prevSibling && nextSibling) {
9119
          newRange.setStartBefore(nextSibling);
9120
          newRange.setEndAfter(prevSibling);
9121
        } else {
9122
          newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
9123
          dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
9124
          newRange.setStartBefore(newCaretPlaceholder);
9125
          newRange.setEndAfter(newCaretPlaceholder);
9126
        }
9127
        this.setSelection(newRange);
9128
        for (var i = caretPlaceholder.length; i--;) {
9129
         caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
9130
        }
9131
9132
      } else {
9133
        // fallback for when all hell breaks loose
9134
        this.contain.focus();
9135
      }
9136
9137
      if (restoreScrollPosition) {
9138
        body.scrollTop  = oldScrollTop;
9139
        body.scrollLeft = oldScrollLeft;
9140
      }
9141
9142
      // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
9143
      try {
9144
        caretPlaceholder.parentNode.removeChild(caretPlaceholder);
9145
      } 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...
9146
    },
9147
9148
    set: function(node, offset) {
9149
      var newRange = rangy.createRange(this.doc);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9150
      newRange.setStart(node, offset || 0);
9151
      this.setSelection(newRange);
9152
    },
9153
9154
    /**
9155
     * Insert html at the caret position and move the cursor after the inserted html
9156
     *
9157
     * @param {String} html HTML string to insert
9158
     * @example
9159
     *    selection.insertHTML("<p>foobar</p>");
9160
     */
9161
    insertHTML: function(html) {
9162
      var range     = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
Unused Code introduced by
The variable range seems to be never used. Consider removing it.
Loading history...
9163
          node = this.doc.createElement('DIV'),
9164
          fragment = this.doc.createDocumentFragment(),
9165
          lastChild;
9166
9167
      node.innerHTML = html;
9168
      lastChild = node.lastChild;
9169
9170
      while (node.firstChild) {
9171
        fragment.appendChild(node.firstChild);
9172
      }
9173
      this.insertNode(fragment);
9174
9175
      if (lastChild) {
9176
        this.setAfter(lastChild);
9177
      }
9178
    },
9179
9180
    /**
9181
     * Insert a node at the caret position and move the cursor behind it
9182
     *
9183
     * @param {Object} node HTML string to insert
9184
     * @example
9185
     *    selection.insertNode(document.createTextNode("foobar"));
9186
     */
9187
    insertNode: function(node) {
9188
      var range = this.getRange();
9189
      if (range) {
9190
        range.insertNode(node);
9191
      }
9192
    },
9193
9194
    /**
9195
     * Wraps current selection with the given node
9196
     *
9197
     * @param {Object} node The node to surround the selected elements with
0 ignored issues
show
Documentation introduced by
The parameter node does not exist. Did you maybe forget to remove this comment?
Loading history...
9198
     */
9199
    surround: function(nodeOptions) {
9200
      var ranges = this.getOwnRanges(),
9201
          node, nodes = [];
9202
      if (ranges.length == 0) {
9203
        return nodes;
9204
      }
9205
9206
      for (var i = ranges.length; i--;) {
9207
        node = this.doc.createElement(nodeOptions.nodeName);
9208
        nodes.push(node);
9209
        if (nodeOptions.className) {
9210
          node.className = nodeOptions.className;
9211
        }
9212
        if (nodeOptions.cssStyle) {
9213
          node.setAttribute('style', nodeOptions.cssStyle);
9214
        }
9215
        try {
9216
          // This only works when the range boundaries are not overlapping other elements
9217
          ranges[i].surroundContents(node);
9218
          this.selectNode(node);
9219
        } catch(e) {
9220
          // fallback
9221
          node.appendChild(ranges[i].extractContents());
9222
          ranges[i].insertNode(node);
9223
        }
9224
      }
9225
      return nodes;
9226
    },
9227
9228
    deblockAndSurround: function(nodeOptions) {
9229
      var tempElement = this.doc.createElement('div'),
9230
          range = rangy.createRange(this.doc),
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
9231
          tempDivElements,
9232
          tempElements,
9233
          firstChild;
9234
9235
      tempElement.className = nodeOptions.className;
9236
9237
      this.composer.commands.exec("formatBlock", nodeOptions.nodeName, nodeOptions.className);
9238
      tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
9239
      if (tempDivElements[0]) {
9240
        tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
9241
9242
        range.setStartBefore(tempDivElements[0]);
9243
        range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
9244
        tempElements = range.extractContents();
9245
9246
        while (tempElements.firstChild) {
9247
          firstChild = tempElements.firstChild;
9248
          if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
9249
            while (firstChild.firstChild) {
9250
              tempElement.appendChild(firstChild.firstChild);
9251
            }
9252
            if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
9253
            tempElements.removeChild(firstChild);
9254
          } else {
9255
            tempElement.appendChild(firstChild);
9256
          }
9257
        }
9258
      } else {
9259
        tempElement = null;
9260
      }
9261
9262
      return tempElement;
9263
    },
9264
9265
    /**
9266
     * Scroll the current caret position into the view
9267
     * FIXME: This is a bit hacky, there might be a smarter way of doing this
9268
     *
9269
     * @example
9270
     *    selection.scrollIntoView();
9271
     */
9272
    scrollIntoView: function() {
9273
      var doc           = this.doc,
9274
          tolerance     = 5, // px
9275
          hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
9276
          tempElement   = doc._wysihtml5ScrollIntoViewElement = doc._wysihtml5ScrollIntoViewElement || (function() {
9277
            var element = doc.createElement("span");
9278
            // The element needs content in order to be able to calculate it's position properly
9279
            element.innerHTML = wysihtml5.INVISIBLE_SPACE;
9280
            return element;
9281
          })(),
9282
          offsetTop;
9283
9284
      if (hasScrollBars) {
9285
        this.insertNode(tempElement);
9286
        offsetTop = _getCumulativeOffsetTop(tempElement);
9287
        tempElement.parentNode.removeChild(tempElement);
9288
        if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
9289
          doc.body.scrollTop = offsetTop;
9290
        }
9291
      }
9292
    },
9293
9294
    /**
9295
     * Select line where the caret is in
9296
     */
9297
    selectLine: function() {
9298
      if (wysihtml5.browser.supportsSelectionModify()) {
9299
        this._selectLine_W3C();
9300
      } else if (this.doc.selection) {
9301
        this._selectLine_MSIE();
9302
      }
9303
    },
9304
9305
    /**
9306
     * See https://developer.mozilla.org/en/DOM/Selection/modify
9307
     */
9308
    _selectLine_W3C: function() {
9309
      var win       = this.doc.defaultView,
9310
          selection = win.getSelection();
9311
      selection.modify("move", "left", "lineboundary");
9312
      selection.modify("extend", "right", "lineboundary");
9313
    },
9314
9315
    _selectLine_MSIE: function() {
9316
      var range       = this.doc.selection.createRange(),
9317
          rangeTop    = range.boundingTop,
9318
          scrollWidth = this.doc.body.scrollWidth,
9319
          rangeBottom,
9320
          rangeEnd,
9321
          measureNode,
9322
          i,
9323
          j;
9324
9325
      if (!range.moveToPoint) {
9326
        return;
9327
      }
9328
9329
      if (rangeTop === 0) {
9330
        // Don't know why, but when the selection ends at the end of a line
9331
        // range.boundingTop is 0
9332
        measureNode = this.doc.createElement("span");
9333
        this.insertNode(measureNode);
9334
        rangeTop = measureNode.offsetTop;
9335
        measureNode.parentNode.removeChild(measureNode);
9336
      }
9337
9338
      rangeTop += 1;
9339
9340
      for (i=-10; i<scrollWidth; i+=2) {
9341
        try {
9342
          range.moveToPoint(i, rangeTop);
9343
          break;
9344
        } 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...
9345
      }
9346
9347
      // Investigate the following in order to handle multi line selections
9348
      // rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
9349
      rangeBottom = rangeTop;
9350
      rangeEnd = this.doc.selection.createRange();
9351
      for (j=scrollWidth; j>=0; j--) {
9352
        try {
9353
          rangeEnd.moveToPoint(j, rangeBottom);
9354
          break;
9355
        } 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...
9356
      }
9357
9358
      range.setEndPoint("EndToEnd", rangeEnd);
9359
      range.select();
9360
    },
9361
9362
    getText: function() {
9363
      var selection = this.getSelection();
9364
      return selection ? selection.toString() : "";
9365
    },
9366
9367
    getNodes: function(nodeType, filter) {
9368
      var range = this.getRange();
9369
      if (range) {
9370
        return range.getNodes([nodeType], filter);
9371
      } else {
9372
        return [];
9373
      }
9374
    },
9375
9376
    fixRangeOverflow: function(range) {
9377
      if (this.contain && this.contain.firstChild && range) {
9378
        var containment = range.compareNode(this.contain);
9379
        if (containment !== 2) {
9380
          if (containment === 1) {
9381
            range.setStartBefore(this.contain.firstChild);
9382
          }
9383
          if (containment === 0) {
9384
            range.setEndAfter(this.contain.lastChild);
9385
          }
9386
          if (containment === 3) {
9387
            range.setStartBefore(this.contain.firstChild);
9388
            range.setEndAfter(this.contain.lastChild);
9389
          }
9390
        } else if (this._detectInlineRangeProblems(range)) {
9391
          var previousElementSibling = range.endContainer.previousElementSibling;
9392
          if (previousElementSibling) {
9393
            range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
9394
          }
9395
        }
9396
      }
9397
    },
9398
9399
    _endOffsetForNode: function(node) {
9400
      var range = document.createRange();
9401
      range.selectNodeContents(node);
9402
      return range.endOffset;
9403
    },
9404
9405
    _detectInlineRangeProblems: function(range) {
9406
      var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
9407
      return (
9408
        range.endOffset == 0 &&
9409
        position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
9410
      );
9411
    },
9412
9413
    getRange: function(dontFix) {
9414
      var selection = this.getSelection(),
9415
          range = selection && selection.rangeCount && selection.getRangeAt(0);
9416
9417
      if (dontFix !== true) {
9418
        this.fixRangeOverflow(range);
9419
      }
9420
9421
      return range;
9422
    },
9423
9424
    getOwnUneditables: function() {
9425
      var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
9426
          deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
9427
9428
      return wysihtml5.lang.array(allUneditables).without(deepUneditables);
9429
    },
9430
9431
    // Returns an array of ranges that belong only to this editable
9432
    // Needed as uneditable block in contenteditabel can split range into pieces
9433
    // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
9434
    getOwnRanges: function()  {
9435
      var ranges = [],
9436
          r = this.getRange(),
9437
          tmpRanges;
9438
9439
      if (r) { ranges.push(r); }
9440
9441
      if (this.unselectableClass && this.contain && r) {
9442
          var uneditables = this.getOwnUneditables(),
9443
              tmpRange;
9444
          if (uneditables.length > 0) {
9445
            for (var i = 0, imax = uneditables.length; i < imax; i++) {
9446
              tmpRanges = [];
9447
              for (var j = 0, jmax = ranges.length; j < jmax; j++) {
9448
                if (ranges[j]) {
9449
                  switch (ranges[j].compareNode(uneditables[i])) {
9450
                    case 2:
9451
                      // all selection inside uneditable. remove
9452
                    break;
9453
                    case 3:
9454
                      //section begins before and ends after uneditable. spilt
9455
                      tmpRange = ranges[j].cloneRange();
9456
                      tmpRange.setEndBefore(uneditables[i]);
9457
                      tmpRanges.push(tmpRange);
9458
9459
                      tmpRange = ranges[j].cloneRange();
9460
                      tmpRange.setStartAfter(uneditables[i]);
9461
                      tmpRanges.push(tmpRange);
9462
                    break;
9463
                    default:
9464
                      // in all other cases uneditable does not touch selection. dont modify
9465
                      tmpRanges.push(ranges[j]);
9466
                  }
9467
                }
9468
                ranges = tmpRanges;
9469
              }
9470
            }
9471
          }
9472
      }
9473
      return ranges;
9474
    },
9475
9476
    getSelection: function() {
9477
      return rangy.getSelection(this.doc.defaultView || this.doc.parentWindow);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

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

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

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

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

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

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

Loading history...
9488
    },
9489
9490
    isCollapsed: function() {
9491
        return this.getSelection().isCollapsed;
9492
    },
9493
9494
    getHtml: function() {
9495
      return this.getSelection().toHtml();
9496
    },
9497
9498
    isEndToEndInNode: function(nodeNames) {
9499
      var range = this.getRange(),
9500
          parentElement = range.commonAncestorContainer,
9501
          startNode = range.startContainer,
9502
          endNode = range.endContainer;
9503
9504
9505
        if (parentElement.nodeType === wysihtml5.TEXT_NODE) {
9506
          parentElement = parentElement.parentNode;
9507
        }
9508
9509
        if (startNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
9510
          return false;
9511
        }
9512
9513
        if (endNode.nodeType === wysihtml5.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
9514
          return false;
9515
        }
9516
9517
        while (startNode && startNode !== parentElement) {
9518
          if (startNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, startNode)) {
9519
            return false;
9520
          }
9521
          if (wysihtml5.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
9522
            return false;
9523
          }
9524
          startNode = startNode.parentNode;
9525
        }
9526
9527
        while (endNode && endNode !== parentElement) {
9528
          if (endNode.nodeType !== wysihtml5.TEXT_NODE && !wysihtml5.dom.contains(parentElement, endNode)) {
9529
            return false;
9530
          }
9531
          if (wysihtml5.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
9532
            return false;
9533
          }
9534
          endNode = endNode.parentNode;
9535
        }
9536
9537
        return (wysihtml5.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
9538
    },
9539
9540
    deselect: function() {
9541
      var sel = this.getSelection();
9542
      sel && sel.removeAllRanges();
9543
    }
9544
  });
9545
9546
})(wysihtml5);
9547
;/**
9548
 * Inspired by the rangy CSS Applier module written by Tim Down and licensed under the MIT license.
9549
 * http://code.google.com/p/rangy/
9550
 *
9551
 * changed in order to be able ...
9552
 *    - to use custom tags
9553
 *    - to detect and replace similar css classes via reg exp
9554
 */
9555
(function(wysihtml5, rangy) {
9556
  var defaultTagName = "span";
9557
9558
  var REG_EXP_WHITE_SPACE = /\s+/g;
9559
9560
  function hasClass(el, cssClass, regExp) {
9561
    if (!el.className) {
9562
      return false;
9563
    }
9564
9565
    var matchingClassNames = el.className.match(regExp) || [];
9566
    return matchingClassNames[matchingClassNames.length - 1] === cssClass;
9567
  }
9568
9569
  function hasStyleAttr(el, regExp) {
9570
    if (!el.getAttribute || !el.getAttribute('style')) {
9571
      return false;
9572
    }
9573
    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...
9574
    return  (el.getAttribute('style').match(regExp)) ? true : false;
9575
  }
9576
9577
  function addStyle(el, cssStyle, regExp) {
9578
    if (el.getAttribute('style')) {
9579
      removeStyle(el, regExp);
9580
      if (el.getAttribute('style') && !(/^\s*$/).test(el.getAttribute('style'))) {
9581
        el.setAttribute('style', cssStyle + ";" + el.getAttribute('style'));
9582
      } else {
9583
        el.setAttribute('style', cssStyle);
9584
      }
9585
    } else {
9586
      el.setAttribute('style', cssStyle);
9587
    }
9588
  }
9589
9590
  function addClass(el, cssClass, regExp) {
9591
    if (el.className) {
9592
      removeClass(el, regExp);
9593
      el.className += " " + cssClass;
9594
    } else {
9595
      el.className = cssClass;
9596
    }
9597
  }
9598
9599
  function removeClass(el, regExp) {
9600
    if (el.className) {
9601
      el.className = el.className.replace(regExp, "");
9602
    }
9603
  }
9604
9605
  function removeStyle(el, regExp) {
9606
    var s,
9607
        s2 = [];
9608
    if (el.getAttribute('style')) {
9609
      s = el.getAttribute('style').split(';');
9610
      for (var i = s.length; i--;) {
9611
        if (!s[i].match(regExp) && !(/^\s*$/).test(s[i])) {
9612
          s2.push(s[i]);
9613
        }
9614
      }
9615
      if (s2.length) {
9616
        el.setAttribute('style', s2.join(';'));
9617
      } else {
9618
        el.removeAttribute('style');
9619
      }
9620
    }
9621
  }
9622
9623
  function getMatchingStyleRegexp(el, style) {
9624
    var regexes = [],
9625
        sSplit = style.split(';'),
9626
        elStyle = el.getAttribute('style');
9627
9628
    if (elStyle) {
9629
      elStyle = elStyle.replace(/\s/gi, '').toLowerCase();
9630
      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"));
9631
9632
      for (var i = sSplit.length; i-- > 0;) {
9633
        if (!(/^\s*$/).test(sSplit[i])) {
9634
          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"));
9635
        }
9636
      }
9637
      for (var j = 0, jmax = regexes.length; j < jmax; j++) {
9638
        if (elStyle.match(regexes[j])) {
9639
          return regexes[j];
9640
        }
9641
      }
9642
    }
9643
9644
    return false;
9645
  }
9646
9647
  function isMatchingAllready(node, tags, style, className) {
9648
    if (style) {
9649
      return getMatchingStyleRegexp(node, style);
9650
    } else if (className) {
9651
      return wysihtml5.dom.hasClass(node, className);
9652
    } else {
9653
      return rangy.dom.arrayContains(tags, node.tagName.toLowerCase());
9654
    }
9655
  }
9656
9657
  function areMatchingAllready(nodes, tags, style, className) {
9658
    for (var i = nodes.length; i--;) {
9659
      if (!isMatchingAllready(nodes[i], tags, style, className)) {
9660
        return false;
9661
      }
9662
    }
9663
    return nodes.length ? true : false;
9664
  }
9665
9666
  function removeOrChangeStyle(el, style, regExp) {
9667
9668
    var exactRegex = getMatchingStyleRegexp(el, style);
9669
    if (exactRegex) {
9670
      // adding same style value on property again removes style
9671
      removeStyle(el, exactRegex);
9672
      return "remove";
9673
    } else {
9674
      // adding new style value changes value
9675
      addStyle(el, style, regExp);
9676
      return "change";
9677
    }
9678
  }
9679
9680
  function hasSameClasses(el1, el2) {
9681
    return el1.className.replace(REG_EXP_WHITE_SPACE, " ") == el2.className.replace(REG_EXP_WHITE_SPACE, " ");
9682
  }
9683
9684
  function replaceWithOwnChildren(el) {
9685
    var parent = el.parentNode;
9686
    while (el.firstChild) {
9687
      parent.insertBefore(el.firstChild, el);
9688
    }
9689
    parent.removeChild(el);
9690
  }
9691
9692
  function elementsHaveSameNonClassAttributes(el1, el2) {
9693
    if (el1.attributes.length != el2.attributes.length) {
9694
      return false;
9695
    }
9696
    for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
9697
      attr1 = el1.attributes[i];
9698
      name = attr1.name;
9699
      if (name != "class") {
9700
        attr2 = el2.attributes.getNamedItem(name);
9701
        if (attr1.specified != attr2.specified) {
9702
          return false;
9703
        }
9704
        if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) {
9705
          return false;
9706
        }
9707
      }
9708
    }
9709
    return true;
9710
  }
9711
9712
  function isSplitPoint(node, offset) {
9713
    if (rangy.dom.isCharacterDataNode(node)) {
9714
      if (offset == 0) {
9715
        return !!node.previousSibling;
9716
      } else if (offset == node.length) {
9717
        return !!node.nextSibling;
9718
      } else {
9719
        return true;
9720
      }
9721
    }
9722
9723
    return offset > 0 && offset < node.childNodes.length;
9724
  }
9725
9726
  function splitNodeAt(node, descendantNode, descendantOffset, container) {
9727
    var newNode;
9728
    if (rangy.dom.isCharacterDataNode(descendantNode)) {
9729
      if (descendantOffset == 0) {
9730
        descendantOffset = rangy.dom.getNodeIndex(descendantNode);
9731
        descendantNode = descendantNode.parentNode;
9732
      } else if (descendantOffset == descendantNode.length) {
9733
        descendantOffset = rangy.dom.getNodeIndex(descendantNode) + 1;
9734
        descendantNode = descendantNode.parentNode;
9735
      } else {
9736
        newNode = rangy.dom.splitDataNode(descendantNode, descendantOffset);
9737
      }
9738
    }
9739
    if (!newNode) {
9740
      if (!container || descendantNode !== container) {
9741
9742
        newNode = descendantNode.cloneNode(false);
9743
        if (newNode.id) {
9744
          newNode.removeAttribute("id");
9745
        }
9746
        var child;
9747
        while ((child = descendantNode.childNodes[descendantOffset])) {
9748
          newNode.appendChild(child);
9749
        }
9750
        rangy.dom.insertAfter(newNode, descendantNode);
9751
9752
      }
9753
    }
9754
    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...
9755
  }
9756
9757
  function Merge(firstNode) {
9758
    this.isElementMerge = (firstNode.nodeType == wysihtml5.ELEMENT_NODE);
9759
    this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
9760
    this.textNodes = [this.firstTextNode];
9761
  }
9762
9763
  Merge.prototype = {
9764
    doMerge: function() {
9765
      var textBits = [], textNode, parent, text;
9766
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9767
        textNode = this.textNodes[i];
9768
        parent = textNode.parentNode;
9769
        textBits[i] = textNode.data;
9770
        if (i) {
9771
          parent.removeChild(textNode);
9772
          if (!parent.hasChildNodes()) {
9773
            parent.parentNode.removeChild(parent);
9774
          }
9775
        }
9776
      }
9777
      this.firstTextNode.data = text = textBits.join("");
9778
      return text;
9779
    },
9780
9781
    getLength: function() {
9782
      var i = this.textNodes.length, len = 0;
9783
      while (i--) {
9784
        len += this.textNodes[i].length;
9785
      }
9786
      return len;
9787
    },
9788
9789
    toString: function() {
9790
      var textBits = [];
9791
      for (var i = 0, len = this.textNodes.length; i < len; ++i) {
9792
        textBits[i] = "'" + this.textNodes[i].data + "'";
9793
      }
9794
      return "[Merge(" + textBits.join(",") + ")]";
9795
    }
9796
  };
9797
9798
  function HTMLApplier(tagNames, cssClass, similarClassRegExp, normalize, cssStyle, similarStyleRegExp, container) {
9799
    this.tagNames = tagNames || [defaultTagName];
9800
    this.cssClass = cssClass || ((cssClass === false) ? false : "");
9801
    this.similarClassRegExp = similarClassRegExp;
9802
    this.cssStyle = cssStyle || "";
9803
    this.similarStyleRegExp = similarStyleRegExp;
9804
    this.normalize = normalize;
9805
    this.applyToAnyTagName = false;
9806
    this.container = container;
9807
  }
9808
9809
  HTMLApplier.prototype = {
9810
    getAncestorWithClass: function(node) {
9811
      var cssClassMatch;
9812
      while (node) {
9813
        cssClassMatch = this.cssClass ? hasClass(node, this.cssClass, this.similarClassRegExp) : (this.cssStyle !== "") ? false : true;
9814
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" &&  rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssClassMatch) {
9815
          return node;
9816
        }
9817
        node = node.parentNode;
9818
      }
9819
      return false;
9820
    },
9821
9822
    // returns parents of node with given style attribute
9823
    getAncestorWithStyle: function(node) {
9824
      var cssStyleMatch;
9825
      while (node) {
9826
        cssStyleMatch = this.cssStyle ? hasStyleAttr(node, this.similarStyleRegExp) : false;
9827
9828
        if (node.nodeType == wysihtml5.ELEMENT_NODE && node.getAttribute("contenteditable") != "false" && rangy.dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && cssStyleMatch) {
9829
          return node;
9830
        }
9831
        node = node.parentNode;
9832
      }
9833
      return false;
9834
    },
9835
9836
    getMatchingAncestor: function(node) {
9837
      var ancestor = this.getAncestorWithClass(node),
9838
          matchType = false;
9839
9840
      if (!ancestor) {
9841
        ancestor = this.getAncestorWithStyle(node);
9842
        if (ancestor) {
9843
          matchType = "style";
9844
        }
9845
      } else {
9846
        if (this.cssStyle) {
9847
          matchType = "class";
9848
        }
9849
      }
9850
9851
      return {
9852
        "element": ancestor,
9853
        "type": matchType
9854
      };
9855
    },
9856
9857
    // Normalizes nodes after applying a CSS class to a Range.
9858
    postApply: function(textNodes, range) {
9859
      var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
9860
9861
      var merges = [], currentMerge;
9862
9863
      var rangeStartNode = firstNode, rangeEndNode = lastNode;
9864
      var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
9865
9866
      var textNode, precedingTextNode;
9867
9868
      for (var i = 0, len = textNodes.length; i < len; ++i) {
9869
        textNode = textNodes[i];
9870
        precedingTextNode = null;
9871
        if (textNode && textNode.parentNode) {
9872
          precedingTextNode = this.getAdjacentMergeableTextNode(textNode.parentNode, false);
9873
        }
9874
        if (precedingTextNode) {
9875
          if (!currentMerge) {
9876
            currentMerge = new Merge(precedingTextNode);
9877
            merges.push(currentMerge);
9878
          }
9879
          currentMerge.textNodes.push(textNode);
9880
          if (textNode === firstNode) {
9881
            rangeStartNode = currentMerge.firstTextNode;
9882
            rangeStartOffset = rangeStartNode.length;
9883
          }
9884
          if (textNode === lastNode) {
9885
            rangeEndNode = currentMerge.firstTextNode;
9886
            rangeEndOffset = currentMerge.getLength();
9887
          }
9888
        } else {
9889
          currentMerge = null;
9890
        }
9891
      }
9892
      // Test whether the first node after the range needs merging
9893
      if(lastNode && lastNode.parentNode) {
9894
        var nextTextNode = this.getAdjacentMergeableTextNode(lastNode.parentNode, true);
9895
        if (nextTextNode) {
9896
          if (!currentMerge) {
9897
            currentMerge = new Merge(lastNode);
9898
            merges.push(currentMerge);
9899
          }
9900
          currentMerge.textNodes.push(nextTextNode);
9901
        }
9902
      }
9903
      // Do the merges
9904
      if (merges.length) {
9905
        for (i = 0, len = merges.length; i < len; ++i) {
9906
          merges[i].doMerge();
9907
        }
9908
        // Set the range boundaries
9909
        range.setStart(rangeStartNode, rangeStartOffset);
9910
        range.setEnd(rangeEndNode, rangeEndOffset);
9911
      }
9912
    },
9913
9914
    getAdjacentMergeableTextNode: function(node, forward) {
9915
        var isTextNode = (node.nodeType == wysihtml5.TEXT_NODE);
9916
        var el = isTextNode ? node.parentNode : node;
9917
        var adjacentNode;
9918
        var propName = forward ? "nextSibling" : "previousSibling";
9919
        if (isTextNode) {
9920
          // Can merge if the node's previous/next sibling is a text node
9921
          adjacentNode = node[propName];
9922
          if (adjacentNode && adjacentNode.nodeType == wysihtml5.TEXT_NODE) {
9923
            return adjacentNode;
9924
          }
9925
        } else {
9926
          // Compare element with its sibling
9927
          adjacentNode = el[propName];
9928
          if (adjacentNode && this.areElementsMergeable(node, adjacentNode)) {
9929
            return adjacentNode[forward ? "firstChild" : "lastChild"];
9930
          }
9931
        }
9932
        return null;
9933
    },
9934
9935
    areElementsMergeable: function(el1, el2) {
9936
      return rangy.dom.arrayContains(this.tagNames, (el1.tagName || "").toLowerCase())
9937
        && rangy.dom.arrayContains(this.tagNames, (el2.tagName || "").toLowerCase())
9938
        && hasSameClasses(el1, el2)
9939
        && elementsHaveSameNonClassAttributes(el1, el2);
9940
    },
9941
9942
    createContainer: function(doc) {
9943
      var el = doc.createElement(this.tagNames[0]);
9944
      if (this.cssClass) {
9945
        el.className = this.cssClass;
9946
      }
9947
      if (this.cssStyle) {
9948
        el.setAttribute('style', this.cssStyle);
9949
      }
9950
      return el;
9951
    },
9952
9953
    applyToTextNode: function(textNode) {
9954
      var parent = textNode.parentNode;
9955
      if (parent.childNodes.length == 1 && rangy.dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
9956
9957
        if (this.cssClass) {
9958
          addClass(parent, this.cssClass, this.similarClassRegExp);
9959
        }
9960
        if (this.cssStyle) {
9961
          addStyle(parent, this.cssStyle, this.similarStyleRegExp);
9962
        }
9963
      } else {
9964
        var el = this.createContainer(rangy.dom.getDocument(textNode));
9965
        textNode.parentNode.insertBefore(el, textNode);
9966
        el.appendChild(textNode);
9967
      }
9968
    },
9969
9970
    isRemovable: function(el) {
9971
      return rangy.dom.arrayContains(this.tagNames, el.tagName.toLowerCase()) &&
9972
              wysihtml5.lang.string(el.className).trim() === "" &&
9973
              (
9974
                !el.getAttribute('style') ||
9975
                wysihtml5.lang.string(el.getAttribute('style')).trim() === ""
9976
              );
9977
    },
9978
9979
    undoToTextNode: function(textNode, range, ancestorWithClass, ancestorWithStyle) {
9980
      var styleMode = (ancestorWithClass) ? false : true,
9981
          ancestor = ancestorWithClass || ancestorWithStyle,
9982
          styleChanged = false;
9983
      if (!range.containsNode(ancestor)) {
9984
        // Split out the portion of the ancestor from which we can remove the CSS class
9985
        var ancestorRange = range.cloneRange();
9986
            ancestorRange.selectNode(ancestor);
9987
9988
        if (ancestorRange.isPointInRange(range.endContainer, range.endOffset) && isSplitPoint(range.endContainer, range.endOffset)) {
9989
            splitNodeAt(ancestor, range.endContainer, range.endOffset, this.container);
9990
            range.setEndAfter(ancestor);
9991
        }
9992
        if (ancestorRange.isPointInRange(range.startContainer, range.startOffset) && isSplitPoint(range.startContainer, range.startOffset)) {
9993
            ancestor = splitNodeAt(ancestor, range.startContainer, range.startOffset, this.container);
9994
        }
9995
      }
9996
9997
      if (!styleMode && this.similarClassRegExp) {
9998
        removeClass(ancestor, this.similarClassRegExp);
9999
      }
10000
10001
      if (styleMode && this.similarStyleRegExp) {
10002
        styleChanged = (removeOrChangeStyle(ancestor, this.cssStyle, this.similarStyleRegExp) === "change");
10003
      }
10004
      if (this.isRemovable(ancestor) && !styleChanged) {
10005
        replaceWithOwnChildren(ancestor);
10006
      }
10007
    },
10008
10009 View Code Duplication
    applyToRange: function(range) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
10010
        var textNodes;
10011
        for (var ri = range.length; ri--;) {
10012
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
10013
10014
            if (!textNodes.length) {
10015
              try {
10016
                var node = this.createContainer(range[ri].endContainer.ownerDocument);
10017
                range[ri].surroundContents(node);
10018
                this.selectNode(range[ri], node);
10019
                return;
10020
              } 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...
10021
            }
10022
10023
            range[ri].splitBoundaries();
10024
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
10025
            if (textNodes.length) {
10026
              var textNode;
10027
10028
              for (var i = 0, len = textNodes.length; i < len; ++i) {
10029
                textNode = textNodes[i];
10030
                if (!this.getMatchingAncestor(textNode).element) {
10031
                  this.applyToTextNode(textNode);
10032
                }
10033
              }
10034
10035
              range[ri].setStart(textNodes[0], 0);
10036
              textNode = textNodes[textNodes.length - 1];
10037
              range[ri].setEnd(textNode, textNode.length);
10038
10039
              if (this.normalize) {
10040
                this.postApply(textNodes, range[ri]);
10041
              }
10042
            }
10043
10044
        }
10045
    },
10046
10047 View Code Duplication
    undoToRange: function(range) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
10048
      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...
10049
      for (var ri = range.length; ri--;) {
10050
10051
          textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
10052
          if (textNodes.length) {
10053
            range[ri].splitBoundaries();
10054
            textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
10055
          } else {
10056
            var doc = range[ri].endContainer.ownerDocument,
10057
                node = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
10058
            range[ri].insertNode(node);
10059
            range[ri].selectNode(node);
10060
            textNodes = [node];
10061
          }
10062
10063
          for (var i = 0, len = textNodes.length; i < len; ++i) {
10064
            if (range[ri].isValid()) {
10065
              textNode = textNodes[i];
10066
10067
              ancestor = this.getMatchingAncestor(textNode);
10068
              if (ancestor.type === "style") {
10069
                this.undoToTextNode(textNode, range[ri], false, ancestor.element);
10070
              } else if (ancestor.element) {
10071
                this.undoToTextNode(textNode, range[ri], ancestor.element);
10072
              }
10073
            }
10074
          }
10075
10076
          if (len == 1) {
10077
            this.selectNode(range[ri], textNodes[0]);
10078
          } else {
10079
            range[ri].setStart(textNodes[0], 0);
10080
            textNode = textNodes[textNodes.length - 1];
10081
            range[ri].setEnd(textNode, textNode.length);
10082
10083
            if (this.normalize) {
10084
              this.postApply(textNodes, range[ri]);
10085
            }
10086
          }
10087
10088
      }
10089
    },
10090
10091
    selectNode: function(range, node) {
10092
      var isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
10093
          canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : true,
10094
          content         = isElement ? node.innerHTML : node.data,
10095
          isEmpty         = (content === "" || content === wysihtml5.INVISIBLE_SPACE);
10096
10097
      if (isEmpty && isElement && canHaveHTML) {
10098
        // Make sure that caret is visible in node by inserting a zero width no breaking space
10099
        try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
10100
      }
10101
      range.selectNodeContents(node);
10102
      if (isEmpty && isElement) {
10103
        range.collapse(false);
10104
      } else if (isEmpty) {
10105
        range.setStartAfter(node);
10106
        range.setEndAfter(node);
10107
      }
10108
    },
10109
10110
    getTextSelectedByRange: function(textNode, range) {
10111
      var textRange = range.cloneRange();
10112
      textRange.selectNodeContents(textNode);
10113
10114
      var intersectionRange = textRange.intersection(range);
10115
      var text = intersectionRange ? intersectionRange.toString() : "";
10116
      textRange.detach();
10117
10118
      return text;
10119
    },
10120
10121
    isAppliedToRange: function(range) {
10122
      var ancestors = [],
10123
          appliedType = "full",
10124
          ancestor, styleAncestor, textNodes;
0 ignored issues
show
Unused Code introduced by
The variable styleAncestor seems to be never used. Consider removing it.
Loading history...
10125
10126
      for (var ri = range.length; ri--;) {
10127
10128
        textNodes = range[ri].getNodes([wysihtml5.TEXT_NODE]);
10129
        if (!textNodes.length) {
10130
          ancestor = this.getMatchingAncestor(range[ri].startContainer).element;
10131
10132
          return (ancestor) ? {
10133
            "elements": [ancestor],
10134
            "coverage": appliedType
10135
          } : false;
10136
        }
10137
10138
        for (var i = 0, len = textNodes.length, selectedText; i < len; ++i) {
10139
          selectedText = this.getTextSelectedByRange(textNodes[i], range[ri]);
10140
          ancestor = this.getMatchingAncestor(textNodes[i]).element;
10141
          if (ancestor && selectedText != "") {
10142
            ancestors.push(ancestor);
10143
10144
            if (wysihtml5.dom.getTextNodes(ancestor, true).length === 1) {
10145
              appliedType = "full";
10146
            } else if (appliedType === "full") {
10147
              appliedType = "inline";
10148
            }
10149
          } else if (!ancestor) {
10150
            appliedType = "partial";
10151
          }
10152
        }
10153
10154
      }
10155
10156
      return (ancestors.length) ? {
10157
        "elements": ancestors,
10158
        "coverage": appliedType
10159
      } : false;
10160
    },
10161
10162
    toggleRange: function(range) {
10163
      var isApplied = this.isAppliedToRange(range),
10164
          parentsExactMatch;
10165
10166
      if (isApplied) {
10167
        if (isApplied.coverage === "full") {
10168
          this.undoToRange(range);
10169
        } else if (isApplied.coverage === "inline") {
10170
          parentsExactMatch = areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass);
10171
          this.undoToRange(range);
10172
          if (!parentsExactMatch) {
10173
            this.applyToRange(range);
10174
          }
10175
        } else {
10176
          // partial
10177
          if (!areMatchingAllready(isApplied.elements, this.tagNames, this.cssStyle, this.cssClass)) {
10178
            this.undoToRange(range);
10179
          }
10180
          this.applyToRange(range);
10181
        }
10182
      } else {
10183
        this.applyToRange(range);
10184
      }
10185
    }
10186
  };
10187
10188
  wysihtml5.selection.HTMLApplier = HTMLApplier;
10189
10190
})(wysihtml5, rangy);
0 ignored issues
show
Bug introduced by
The variable rangy seems to be never declared. If this is a global, consider adding a /** global: rangy */ comment.

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

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

Loading history...
10191
;/**
10192
 * Rich Text Query/Formatting Commands
10193
 *
10194
 * @example
10195
 *    var commands = new wysihtml5.Commands(editor);
10196
 */
10197
wysihtml5.Commands = Base.extend(
10198
  /** @scope wysihtml5.Commands.prototype */ {
10199
  constructor: function(editor) {
10200
    this.editor   = editor;
10201
    this.composer = editor.composer;
10202
    this.doc      = this.composer.doc;
10203
  },
10204
10205
  /**
10206
   * Check whether the browser supports the given command
10207
   *
10208
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
10209
   * @example
10210
   *    commands.supports("createLink");
10211
   */
10212
  support: function(command) {
10213
    return wysihtml5.browser.supportsCommand(this.doc, command);
10214
  },
10215
10216
  /**
10217
   * Check whether the browser supports the given command
10218
   *
10219
   * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
10220
   * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
10221
   * @example
10222
   *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
10223
   */
10224
  exec: function(command, value) {
10225
    var obj     = wysihtml5.commands[command],
10226
        args    = wysihtml5.lang.array(arguments).get(),
10227
        method  = obj && obj.exec,
10228
        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...
10229
10230
    this.editor.fire("beforecommand:composer");
10231
10232
    if (method) {
10233
      args.unshift(this.composer);
10234
      result = method.apply(obj, args);
10235
    } else {
10236
      try {
10237
        // try/catch for buggy firefox
10238
        result = this.doc.execCommand(command, false, value);
10239
      } 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...
10240
    }
10241
10242
    this.editor.fire("aftercommand:composer");
10243
    return result;
10244
  },
10245
10246
  /**
10247
   * Check whether the current command is active
10248
   * If the caret is within a bold text, then calling this with command "bold" should return true
10249
   *
10250
   * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
10251
   * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
10252
   * @return {Boolean} Whether the command is active
10253
   * @example
10254
   *    var isCurrentSelectionBold = commands.state("bold");
10255
   */
10256
  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...
10257
    var obj     = wysihtml5.commands[command],
10258
        args    = wysihtml5.lang.array(arguments).get(),
10259
        method  = obj && obj.state;
10260
    if (method) {
10261
      args.unshift(this.composer);
10262
      return method.apply(obj, args);
10263
    } else {
10264
      try {
10265
        // try/catch for buggy firefox
10266
        return this.doc.queryCommandState(command);
10267
      } catch(e) {
10268
        return false;
10269
      }
10270
    }
10271
  },
10272
10273
  /* Get command state parsed value if command has stateValue parsing function */
10274
  stateValue: function(command) {
10275
    var obj     = wysihtml5.commands[command],
10276
        args    = wysihtml5.lang.array(arguments).get(),
10277
        method  = obj && obj.stateValue;
10278
    if (method) {
10279
      args.unshift(this.composer);
10280
      return method.apply(obj, args);
10281
    } else {
10282
      return false;
10283
    }
10284
  }
10285
});
10286
;wysihtml5.commands.bold = {
10287
  exec: function(composer, command) {
10288
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "b");
10289
  },
10290
10291
  state: function(composer, command) {
10292
    // element.ownerDocument.queryCommandState("bold") results:
10293
    // firefox: only <b>
10294
    // chrome:  <b>, <strong>, <h1>, <h2>, ...
10295
    // ie:      <b>, <strong>
10296
    // opera:   <b>, <strong>
10297
    return wysihtml5.commands.formatInline.state(composer, command, "b");
10298
  }
10299
};
10300
10301
;(function(wysihtml5) {
10302
  var undef,
10303
      NODE_NAME = "A",
10304
      dom       = wysihtml5.dom;
10305
10306
  function _format(composer, attributes) {
10307
    var doc             = composer.doc,
10308
        tempClass       = "_wysihtml5-temp-" + (+new Date()),
10309
        tempClassRegExp = /non-matching-class/g,
10310
        i               = 0,
10311
        length,
10312
        anchors,
10313
        anchor,
10314
        hasElementChild,
10315
        isEmpty,
10316
        elementToSetCaretAfter,
10317
        textContent,
10318
        whiteSpace,
10319
        j;
10320
    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...
10321
    anchors = doc.querySelectorAll(NODE_NAME + "." + tempClass);
10322
    length  = anchors.length;
10323
    for (; i<length; i++) {
10324
      anchor = anchors[i];
10325
      anchor.removeAttribute("class");
10326
      for (j in attributes) {
10327
        // Do not set attribute "text" as it is meant for setting string value if created link has no textual data
10328
        if (j !== "text") {
10329
          anchor.setAttribute(j, attributes[j]);
10330
        }
10331
      }
10332
    }
10333
10334
    elementToSetCaretAfter = anchor;
0 ignored issues
show
Bug introduced by
The variable anchor seems to not be initialized for all possible execution paths.
Loading history...
10335
    if (length === 1) {
10336
      textContent = dom.getTextContent(anchor);
10337
      hasElementChild = !!anchor.querySelector("*");
10338
      isEmpty = textContent === "" || textContent === wysihtml5.INVISIBLE_SPACE;
10339
      if (!hasElementChild && isEmpty) {
10340
        dom.setTextContent(anchor, attributes.text || anchor.href);
10341
        whiteSpace = doc.createTextNode(" ");
10342
        composer.selection.setAfter(anchor);
10343
        dom.insert(whiteSpace).after(anchor);
10344
        elementToSetCaretAfter = whiteSpace;
10345
      }
10346
    }
10347
    composer.selection.setAfter(elementToSetCaretAfter);
10348
  }
10349
10350
  // Changes attributes of links
10351
  function _changeLinks(composer, anchors, attributes) {
10352
    var oldAttrs;
10353
    for (var a = anchors.length; a--;) {
10354
10355
      // Remove all old attributes
10356
      oldAttrs = anchors[a].attributes;
10357
      for (var oa = oldAttrs.length; oa--;) {
10358
        anchors[a].removeAttribute(oldAttrs.item(oa).name);
10359
      }
10360
10361
      // Set new attributes
10362
      for (var j in attributes) {
10363
        if (attributes.hasOwnProperty(j)) {
10364
          anchors[a].setAttribute(j, attributes[j]);
10365
        }
10366
      }
10367
10368
    }
10369
  }
10370
10371
  wysihtml5.commands.createLink = {
10372
    /**
10373
     * TODO: Use HTMLApplier or formatInline here
10374
     *
10375
     * Turns selection into a link
10376
     * If selection is already a link, it just changes the attributes
10377
     *
10378
     * @example
10379
     *    // either ...
10380
     *    wysihtml5.commands.createLink.exec(composer, "createLink", "http://www.google.de");
10381
     *    // ... or ...
10382
     *    wysihtml5.commands.createLink.exec(composer, "createLink", { href: "http://www.google.de", target: "_blank" });
10383
     */
10384
    exec: function(composer, command, value) {
10385
      var anchors = this.state(composer, command);
10386
      if (anchors) {
10387
        // Selection contains links then change attributes of these links
10388
        composer.selection.executeAndRestore(function() {
10389
          _changeLinks(composer, anchors, value);
10390
        });
10391
      } else {
10392
        // Create links
10393
        value = typeof(value) === "object" ? value : { href: value };
10394
        _format(composer, value);
10395
      }
10396
    },
10397
10398
    state: function(composer, command) {
10399
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10400
    }
10401
  };
10402
})(wysihtml5);
10403
;(function(wysihtml5) {
10404
  var dom = wysihtml5.dom;
10405
10406
  function _removeFormat(composer, anchors) {
10407
    var length  = anchors.length,
10408
        i       = 0,
10409
        anchor,
10410
        codeElement,
10411
        textContent;
10412
    for (; i<length; i++) {
10413
      anchor      = anchors[i];
10414
      codeElement = dom.getParentElement(anchor, { nodeName: "code" });
10415
      textContent = dom.getTextContent(anchor);
10416
10417
      // if <a> contains url-like text content, rename it to <code> to prevent re-autolinking
10418
      // else replace <a> with its childNodes
10419
      if (textContent.match(dom.autoLink.URL_REG_EXP) && !codeElement) {
10420
        // <code> element is used to prevent later auto-linking of the content
10421
        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...
10422
      } else {
10423
        dom.replaceWithChildNodes(anchor);
10424
      }
10425
    }
10426
  }
10427
10428
  wysihtml5.commands.removeLink = {
10429
    /*
10430
     * If selection is a link, it removes the link and wraps it with a <code> element
10431
     * The <code> element is needed to avoid auto linking
10432
     *
10433
     * @example
10434
     *    wysihtml5.commands.createLink.exec(composer, "removeLink");
10435
     */
10436
10437
    exec: function(composer, command) {
10438
      var anchors = this.state(composer, command);
10439
      if (anchors) {
10440
        composer.selection.executeAndRestore(function() {
10441
          _removeFormat(composer, anchors);
10442
        });
10443
      }
10444
    },
10445
10446
    state: function(composer, command) {
10447
      return wysihtml5.commands.formatInline.state(composer, command, "A");
10448
    }
10449
  };
10450
})(wysihtml5);
10451
;/**
10452
 * document.execCommand("fontSize") will create either inline styles (firefox, chrome) or use font tags
10453
 * which we don't want
10454
 * Instead we set a css class
10455
 */
10456
(function(wysihtml5) {
10457
  var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g;
10458
10459
  wysihtml5.commands.fontSize = {
10460
    exec: function(composer, command, size) {
10461
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10462
    },
10463
10464
    state: function(composer, command, size) {
10465
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-font-size-" + size, REG_EXP);
10466
    }
10467
  };
10468
})(wysihtml5);
10469
;/* In case font size adjustment to any number defined by user is preferred, we cannot use classes and must use inline styles. */
10470
(function(wysihtml5) {
10471
  var REG_EXP = /(\s|^)font-size\s*:\s*[^;\s]+;?/gi;
10472
10473
  wysihtml5.commands.fontSizeStyle = {
10474
    exec: function(composer, command, size) {
10475
      size = (typeof(size) == "object") ? size.size : size;
10476
      if (!(/^\s*$/).test(size)) {
10477
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, "font-size:" + size, REG_EXP);
10478
      }
10479
    },
10480
10481
    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...
10482
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "font-size", REG_EXP);
10483
    },
10484
10485
    stateValue: function(composer, command) {
10486
      var st = this.state(composer, command),
10487
          styleStr, fontsizeMatches,
0 ignored issues
show
Unused Code introduced by
The variable fontsizeMatches seems to be never used. Consider removing it.
Loading history...
10488
          val = false;
0 ignored issues
show
Unused Code introduced by
The variable val seems to be never used. Consider removing it.
Loading history...
10489
10490
      if (st && wysihtml5.lang.object(st).isArray()) {
10491
          st = st[0];
10492
      }
10493
      if (st) {
10494
        styleStr = st.getAttribute('style');
10495
        if (styleStr) {
10496
          return wysihtml5.quirks.styleParser.parseFontSize(styleStr);
10497
        }
10498
      }
10499
      return false;
10500
    }
10501
  };
10502
})(wysihtml5);
10503
;/**
10504
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10505
 * which we don't want
10506
 * Instead we set a css class
10507
 */
10508
(function(wysihtml5) {
10509
  var REG_EXP = /wysiwyg-color-[0-9a-z]+/g;
10510
10511
  wysihtml5.commands.foreColor = {
10512
    exec: function(composer, command, color) {
10513
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10514
    },
10515
10516
    state: function(composer, command, color) {
10517
      return wysihtml5.commands.formatInline.state(composer, command, "span", "wysiwyg-color-" + color, REG_EXP);
10518
    }
10519
  };
10520
})(wysihtml5);
10521
;/**
10522
 * document.execCommand("foreColor") will create either inline styles (firefox, chrome) or use font tags
10523
 * which we don't want
10524
 * Instead we set a css class
10525
 */
10526 View Code Duplication
(function(wysihtml5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
10527
  var REG_EXP = /(\s|^)color\s*:\s*[^;\s]+;?/gi;
10528
10529
  wysihtml5.commands.foreColorStyle = {
10530
    exec: function(composer, command, color) {
10531
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "color:" + color.color : "color:" + color, "color"),
10532
          colString;
10533
10534
      if (colorVals) {
10535
        colString = "color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10536
        if (colorVals[3] !== 1) {
10537
          colString += "color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10538
        }
10539
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10540
      }
10541
    },
10542
10543
    state: function(composer, command) {
10544
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "color", REG_EXP);
10545
    },
10546
10547
    stateValue: function(composer, command, props) {
10548
      var st = this.state(composer, command),
10549
          colorStr;
10550
10551
      if (st && wysihtml5.lang.object(st).isArray()) {
10552
        st = st[0];
10553
      }
10554
10555
      if (st) {
10556
        colorStr = st.getAttribute('style');
10557
        if (colorStr) {
10558
          if (colorStr) {
10559
            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...
10560
            return wysihtml5.quirks.styleParser.unparseColor(val, props);
10561
          }
10562
        }
10563
      }
10564
      return false;
10565
    }
10566
10567
  };
10568
})(wysihtml5);
10569
;/* In case background adjustment to any color defined by user is preferred, we cannot use classes and must use inline styles. */
10570 View Code Duplication
(function(wysihtml5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
10571
  var REG_EXP = /(\s|^)background-color\s*:\s*[^;\s]+;?/gi;
10572
10573
  wysihtml5.commands.bgColorStyle = {
10574
    exec: function(composer, command, color) {
10575
      var colorVals  = wysihtml5.quirks.styleParser.parseColor((typeof(color) == "object") ? "background-color:" + color.color : "background-color:" + color, "background-color"),
10576
          colString;
10577
10578
      if (colorVals) {
10579
        colString = "background-color: rgb(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ');';
10580
        if (colorVals[3] !== 1) {
10581
          colString += "background-color: rgba(" + colorVals[0] + ',' + colorVals[1] + ',' + colorVals[2] + ',' + colorVals[3] + ');';
10582
        }
10583
        wysihtml5.commands.formatInline.execWithToggle(composer, command, "span", false, false, colString, REG_EXP);
10584
      }
10585
    },
10586
10587
    state: function(composer, command) {
10588
      return wysihtml5.commands.formatInline.state(composer, command, "span", false, false, "background-color", REG_EXP);
10589
    },
10590
10591
    stateValue: function(composer, command, props) {
10592
      var st = this.state(composer, command),
10593
          colorStr,
10594
          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...
10595
10596
      if (st && wysihtml5.lang.object(st).isArray()) {
10597
        st = st[0];
10598
      }
10599
10600
      if (st) {
10601
        colorStr = st.getAttribute('style');
10602
        if (colorStr) {
10603
          val = wysihtml5.quirks.styleParser.parseColor(colorStr, "background-color");
10604
          return wysihtml5.quirks.styleParser.unparseColor(val, props);
10605
        }
10606
      }
10607
      return false;
10608
    }
10609
10610
  };
10611
})(wysihtml5);
10612
;(function(wysihtml5) {
10613
  var dom                     = wysihtml5.dom,
10614
      // Following elements are grouped
10615
      // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
10616
      // instead of creating a H4 within a H1 which would result in semantically invalid html
10617
      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE", "DIV"];
10618
10619
  /**
10620
   * Remove similiar classes (based on classRegExp)
10621
   * and add the desired class name
10622
   */
10623
  function _addClass(element, className, classRegExp) {
10624
    if (element.className) {
10625
      _removeClass(element, classRegExp);
10626
      element.className = wysihtml5.lang.string(element.className + " " + className).trim();
10627
    } else {
10628
      element.className = className;
10629
    }
10630
  }
10631
10632
  function _addStyle(element, cssStyle, styleRegExp) {
10633
    _removeStyle(element, styleRegExp);
10634
    if (element.getAttribute('style')) {
10635
      element.setAttribute('style', wysihtml5.lang.string(element.getAttribute('style') + " " + cssStyle).trim());
10636
    } else {
10637
      element.setAttribute('style', cssStyle);
10638
    }
10639
  }
10640
10641
  function _removeClass(element, classRegExp) {
10642
    var ret = classRegExp.test(element.className);
10643
    element.className = element.className.replace(classRegExp, "");
10644
    if (wysihtml5.lang.string(element.className).trim() == '') {
10645
        element.removeAttribute('class');
10646
    }
10647
    return ret;
10648
  }
10649
10650
  function _removeStyle(element, styleRegExp) {
10651
    var ret = styleRegExp.test(element.getAttribute('style'));
10652
    element.setAttribute('style', (element.getAttribute('style') || "").replace(styleRegExp, ""));
10653
    if (wysihtml5.lang.string(element.getAttribute('style') || "").trim() == '') {
10654
      element.removeAttribute('style');
10655
    }
10656
    return ret;
10657
  }
10658
10659
  function _removeLastChildIfLineBreak(node) {
10660
    var lastChild = node.lastChild;
10661
    if (lastChild && _isLineBreak(lastChild)) {
10662
      lastChild.parentNode.removeChild(lastChild);
10663
    }
10664
  }
10665
10666
  function _isLineBreak(node) {
10667
    return node.nodeName === "BR";
10668
  }
10669
10670
  /**
10671
   * Execute native query command
10672
   * and if necessary modify the inserted node's className
10673
   */
10674
  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...
10675
    var ranges = composer.selection.getOwnRanges();
10676
    for (var i = ranges.length; i--;){
10677
      composer.selection.getSelection().removeAllRanges();
10678
      composer.selection.setSelection(ranges[i]);
10679
      if (className) {
10680
        var eventListener = dom.observe(doc, "DOMNodeInserted", function(event) {
10681
          var target = event.target,
10682
              displayStyle;
10683
          if (target.nodeType !== wysihtml5.ELEMENT_NODE) {
10684
            return;
10685
          }
10686
          displayStyle = dom.getStyle("display").from(target);
10687
          if (displayStyle.substr(0, 6) !== "inline") {
10688
            // Make sure that only block elements receive the given class
10689
            target.className += " " + className;
10690
          }
10691
        });
10692
      }
10693
      doc.execCommand(command, false, nodeName);
10694
10695
      if (eventListener) {
10696
        eventListener.stop();
10697
      }
10698
    }
10699
  }
10700
10701
  function _selectionWrap(composer, options) {
10702
    if (composer.selection.isCollapsed()) {
10703
        composer.selection.selectLine();
10704
    }
10705
10706
    var surroundedNodes = composer.selection.surround(options);
10707
    for (var i = 0, imax = surroundedNodes.length; i < imax; i++) {
10708
      wysihtml5.dom.lineBreaks(surroundedNodes[i]).remove();
10709
      _removeLastChildIfLineBreak(surroundedNodes[i]);
10710
    }
10711
10712
    // rethink restoring selection
10713
    // composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
10714
  }
10715
10716
  function _hasClasses(element) {
10717
    return !!wysihtml5.lang.string(element.className).trim();
10718
  }
10719
10720
  function _hasStyles(element) {
10721
    return !!wysihtml5.lang.string(element.getAttribute('style') || '').trim();
10722
  }
10723
10724
  wysihtml5.commands.formatBlock = {
10725
    exec: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10726
      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...
10727
          blockElements    = this.state(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp),
10728
          useLineBreaks   = composer.config.useLineBreaks,
10729
          defaultNodeName = useLineBreaks ? "DIV" : "P",
10730
          selectedNodes, classRemoveAction, blockRenameFound, styleRemoveAction, blockElement;
10731
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10732
10733
      if (blockElements.length) {
10734
        composer.selection.executeAndRestoreRangy(function() {
10735
          for (var b = blockElements.length; b--;) {
10736
            if (classRegExp) {
10737
              classRemoveAction = _removeClass(blockElements[b], classRegExp);
10738
            }
10739
            if (styleRegExp) {
10740
              styleRemoveAction = _removeStyle(blockElements[b], styleRegExp);
10741
            }
10742
10743
            if ((styleRemoveAction || classRemoveAction) && nodeName === null && blockElements[b].nodeName != defaultNodeName) {
10744
              // dont rename or remove element when just setting block formating class or style
10745
              return;
10746
            }
10747
10748
            var hasClasses = _hasClasses(blockElements[b]),
10749
                hasStyles = _hasStyles(blockElements[b]);
10750
10751
            if (!hasClasses && !hasStyles && (useLineBreaks || nodeName === "P")) {
10752
              // Insert a line break afterwards and beforewards when there are siblings
10753
              // that are not of type line break or block element
10754
              wysihtml5.dom.lineBreaks(blockElements[b]).add();
10755
              dom.replaceWithChildNodes(blockElements[b]);
10756
            } else {
10757
              // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
10758
              dom.renameElement(blockElements[b], nodeName === "P" ? "DIV" : defaultNodeName);
10759
            }
10760
          }
10761
        });
10762
10763
        return;
10764
      }
10765
10766
      // Find similiar block element and rename it (<h2 class="foo"></h2>  =>  <h1 class="foo"></h1>)
10767
      if (nodeName === null || wysihtml5.lang.array(BLOCK_ELEMENTS_GROUP).contains(nodeName)) {
10768
        selectedNodes = composer.selection.findNodesInSelection(BLOCK_ELEMENTS_GROUP).concat(composer.selection.getSelectedOwnNodes());
10769
        composer.selection.executeAndRestoreRangy(function() {
10770
          for (var n = selectedNodes.length; n--;) {
10771
            blockElement = dom.getParentElement(selectedNodes[n], {
10772
              nodeName: BLOCK_ELEMENTS_GROUP
10773
            });
10774
            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...ELEMENTS_GROUP,false)}) on line 10771. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
10775
              blockElement = null;
10776
            }
10777
            if (blockElement) {
10778
                // Rename current block element to new block element and add class
10779
                if (nodeName) {
10780
                  blockElement = dom.renameElement(blockElement, nodeName);
10781
                }
10782
                if (className) {
10783
                  _addClass(blockElement, className, classRegExp);
10784
                }
10785
                if (cssStyle) {
10786
                  _addStyle(blockElement, cssStyle, styleRegExp);
10787
                }
10788
              blockRenameFound = true;
10789
            }
10790
          }
10791
10792
        });
10793
10794
        if (blockRenameFound) {
10795
          return;
10796
        }
10797
      }
10798
10799
      _selectionWrap(composer, {
10800
        "nodeName": (nodeName || defaultNodeName),
10801
        "className": className || null,
10802
        "cssStyle": cssStyle || null
10803
      });
10804
    },
10805
10806
    state: function(composer, command, nodeName, className, classRegExp, cssStyle, styleRegExp) {
10807
      var nodes = composer.selection.getSelectedOwnNodes(),
10808
          parents = [],
10809
          parent;
10810
10811
      nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
10812
10813
      //var selectedNode = composer.selection.getSelectedNode();
10814
      for (var i = 0, maxi = nodes.length; i < maxi; i++) {
10815
        parent = dom.getParentElement(nodes[i], {
10816
          nodeName:     nodeName,
10817
          className:    className,
10818
          classRegExp:  classRegExp,
10819
          cssStyle:     cssStyle,
10820
          styleRegExp:  styleRegExp
10821
        });
10822
        if (parent && wysihtml5.lang.array(parents).indexOf(parent) == -1) {
10823
          parents.push(parent);
10824
        }
10825
      }
10826
      if (parents.length == 0) {
10827
        return false;
10828
      }
10829
      return parents;
10830
    }
10831
10832
10833
  };
10834
})(wysihtml5);
10835
;/* Formats block for as a <pre><code class="classname"></code></pre> block
10836
 * Useful in conjuction for sytax highlight utility: highlight.js
10837
 *
10838
 * Usage:
10839
 *
10840
 * editorInstance.composer.commands.exec("formatCode", "language-html");
10841
*/
10842
10843
wysihtml5.commands.formatCode = {
10844
10845
  exec: function(composer, command, classname) {
10846
    var pre = this.state(composer),
10847
        code, range, selectedNodes;
10848
    if (pre) {
10849
      // caret is already within a <pre><code>...</code></pre>
10850
      composer.selection.executeAndRestore(function() {
10851
        code = pre.querySelector("code");
10852
        wysihtml5.dom.replaceWithChildNodes(pre);
10853
        if (code) {
10854
          wysihtml5.dom.replaceWithChildNodes(code);
10855
        }
10856
      });
10857
    } else {
10858
      // Wrap in <pre><code>...</code></pre>
10859
      range = composer.selection.getRange();
10860
      selectedNodes = range.extractContents();
10861
      pre = composer.doc.createElement("pre");
10862
      code = composer.doc.createElement("code");
10863
10864
      if (classname) {
10865
        code.className = classname;
10866
      }
10867
10868
      pre.appendChild(code);
10869
      code.appendChild(selectedNodes);
10870
      range.insertNode(pre);
10871
      composer.selection.selectNode(pre);
10872
    }
10873
  },
10874
10875
  state: function(composer) {
10876
    var selectedNode = composer.selection.getSelectedNode();
10877
    if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&&
10878
        selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") {
10879
      return selectedNode;
10880
    } else {
10881
      return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
10882
    }
10883
  }
10884
};;/**
10885
 * formatInline scenarios for tag "B" (| = caret, |foo| = selected text)
10886
 *
10887
 *   #1 caret in unformatted text:
10888
 *      abcdefg|
10889
 *   output:
10890
 *      abcdefg<b>|</b>
10891
 *
10892
 *   #2 unformatted text selected:
10893
 *      abc|deg|h
10894
 *   output:
10895
 *      abc<b>|deg|</b>h
10896
 *
10897
 *   #3 unformatted text selected across boundaries:
10898
 *      ab|c <span>defg|h</span>
10899
 *   output:
10900
 *      ab<b>|c </b><span><b>defg</b>|h</span>
10901
 *
10902
 *   #4 formatted text entirely selected
10903
 *      <b>|abc|</b>
10904
 *   output:
10905
 *      |abc|
10906
 *
10907
 *   #5 formatted text partially selected
10908
 *      <b>ab|c|</b>
10909
 *   output:
10910
 *      <b>ab</b>|c|
10911
 *
10912
 *   #6 formatted text selected across boundaries
10913
 *      <span>ab|c</span> <b>de|fgh</b>
10914
 *   output:
10915
 *      <span>ab|c</span> de|<b>fgh</b>
10916
 */
10917
(function(wysihtml5) {
10918
  var // Treat <b> as <strong> and vice versa
10919
      ALIAS_MAPPING = {
10920
        "strong": "b",
10921
        "em":     "i",
10922
        "b":      "strong",
10923
        "i":      "em"
10924
      },
10925
      htmlApplier = {};
10926
10927
  function _getTagNames(tagName) {
10928
    var alias = ALIAS_MAPPING[tagName];
10929
    return alias ? [tagName.toLowerCase(), alias.toLowerCase()] : [tagName.toLowerCase()];
10930
  }
10931
10932
  function _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, container) {
10933
    var identifier = tagName;
10934
    
10935
    if (className) {
10936
      identifier += ":" + className;
10937
    }
10938
    if (cssStyle) {
10939
      identifier += ":" + cssStyle;
10940
    }
10941
10942
    if (!htmlApplier[identifier]) {
10943
      htmlApplier[identifier] = new wysihtml5.selection.HTMLApplier(_getTagNames(tagName), className, classRegExp, true, cssStyle, styleRegExp, container);
10944
    }
10945
10946
    return htmlApplier[identifier];
10947
  }
10948
10949
  wysihtml5.commands.formatInline = {
10950
    exec: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, dontRestoreSelect, noCleanup) {
10951
      var range = composer.selection.createRange(),
10952
          ownRanges = composer.selection.getOwnRanges();
10953
10954
      if (!ownRanges || ownRanges.length == 0) {
10955
        return false;
10956
      }
10957
      composer.selection.getSelection().removeAllRanges();
10958
10959
      _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).toggleRange(ownRanges);
10960
10961
      if (!dontRestoreSelect) {
10962
        range.setStart(ownRanges[0].startContainer,  ownRanges[0].startOffset);
10963
        range.setEnd(
10964
          ownRanges[ownRanges.length - 1].endContainer,
10965
          ownRanges[ownRanges.length - 1].endOffset
10966
        );
10967
        composer.selection.setSelection(range);
10968
        composer.selection.executeAndRestore(function() {
10969
          if (!noCleanup) {
10970
            composer.cleanUp();
10971
          }
10972
        }, 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...
10973
      } 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...
10974
        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...
10975
      }
10976
    },
10977
10978
    // Executes so that if collapsed caret is in a state and executing that state it should unformat that state
10979
    // It is achieved by selecting the entire state element before executing.
10980
    // This works on built in contenteditable inline format commands
10981
    execWithToggle: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
10982
      var that = this;
10983
10984
      if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) &&
10985
        composer.selection.isCollapsed() &&
10986
        !composer.selection.caretIsLastInSelection() &&
10987
        !composer.selection.caretIsFirstInSelection()
10988
      ) {
10989
        var state_element = that.state(composer, command, tagName, className, classRegExp)[0];
10990
        composer.selection.executeAndRestoreRangy(function() {
10991
          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...
10992
          composer.selection.selectNode(state_element, true);
10993
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10994
        });
10995
      } else {
10996
        if (this.state(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) && !composer.selection.isCollapsed()) {
10997
          composer.selection.executeAndRestoreRangy(function() {
10998
            wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp, true, true);
10999
          });
11000
        } else {
11001
          wysihtml5.commands.formatInline.exec(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp);
11002
        }
11003
      }
11004
    },
11005
11006
    state: function(composer, command, tagName, className, classRegExp, cssStyle, styleRegExp) {
11007
      var doc           = composer.doc,
11008
          aliasTagName  = ALIAS_MAPPING[tagName] || tagName,
11009
          ownRanges, isApplied;
11010
11011
      // Check whether the document contains a node with the desired tagName
11012
      if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
11013
          !wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
11014
        return false;
11015
      }
11016
11017
       // Check whether the document contains a node with the desired className
11018
      if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
11019
         return false;
11020
      }
11021
11022
      ownRanges = composer.selection.getOwnRanges();
11023
11024
      if (!ownRanges || ownRanges.length === 0) {
11025
        return false;
11026
      }
11027
11028
      isApplied = _getApplier(tagName, className, classRegExp, cssStyle, styleRegExp, composer.element).isAppliedToRange(ownRanges);
11029
11030
      return (isApplied && isApplied.elements) ? isApplied.elements : false;
11031
    }
11032
  };
11033
})(wysihtml5);
11034
;(function(wysihtml5) {
11035
11036
  wysihtml5.commands.insertBlockQuote = {
11037
    exec: function(composer, command) {
11038
      var state = this.state(composer, command),
11039
          endToEndParent = composer.selection.isEndToEndInNode(['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P']),
11040
          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...
11041
11042
      composer.selection.executeAndRestore(function() {
11043
        if (state) {
11044
          if (composer.config.useLineBreaks) {
11045
             wysihtml5.dom.lineBreaks(state).add();
11046
          }
11047
          wysihtml5.dom.unwrap(state);
11048
        } else {
11049
          if (composer.selection.isCollapsed()) {
11050
            composer.selection.selectLine();
11051
          }
11052
          
11053
          if (endToEndParent) {
11054
            var qouteEl = endToEndParent.ownerDocument.createElement('blockquote');
11055
            wysihtml5.dom.insert(qouteEl).after(endToEndParent);
11056
            qouteEl.appendChild(endToEndParent);
11057
          } else {
11058
            composer.selection.surround({nodeName: "blockquote"});
11059
          }
11060
        }
11061
      });
11062
    },
11063
    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...
11064
      var selectedNode  = composer.selection.getSelectedNode(),
11065
          node = wysihtml5.dom.getParentElement(selectedNode, { nodeName: "BLOCKQUOTE" }, false, composer.element);
11066
11067
      return (node) ? node : false;
11068
    }
11069
  };
11070
11071
})(wysihtml5);;wysihtml5.commands.insertHTML = {
11072
  exec: function(composer, command, html) {
11073
    if (composer.commands.support(command)) {
11074
      composer.doc.execCommand(command, false, html);
11075
    } else {
11076
      composer.selection.insertHTML(html);
11077
    }
11078
  },
11079
11080
  state: function() {
11081
    return false;
11082
  }
11083
};
11084
;(function(wysihtml5) {
11085
  var NODE_NAME = "IMG";
11086
11087
  wysihtml5.commands.insertImage = {
11088
    /**
11089
     * Inserts an <img>
11090
     * If selection is already an image link, it removes it
11091
     *
11092
     * @example
11093
     *    // either ...
11094
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg");
11095
     *    // ... or ...
11096
     *    wysihtml5.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" });
11097
     */
11098
    exec: function(composer, command, value) {
11099
      value = typeof(value) === "object" ? value : { src: value };
11100
11101
      var doc     = composer.doc,
11102
          image   = this.state(composer),
11103
          textNode,
11104
          parent;
11105
11106
      if (image) {
11107
        // Image already selected, set the caret before it and delete it
11108
        composer.selection.setBefore(image);
11109
        parent = image.parentNode;
11110
        parent.removeChild(image);
11111
11112
        // and it's parent <a> too if it hasn't got any other relevant child nodes
11113
        wysihtml5.dom.removeEmptyTextNodes(parent);
11114
        if (parent.nodeName === "A" && !parent.firstChild) {
11115
          composer.selection.setAfter(parent);
11116
          parent.parentNode.removeChild(parent);
11117
        }
11118
11119
        // firefox and ie sometimes don't remove the image handles, even though the image got removed
11120
        wysihtml5.quirks.redraw(composer.element);
11121
        return;
11122
      }
11123
11124
      image = doc.createElement(NODE_NAME);
11125
11126
      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...
11127
        image.setAttribute(i === "className" ? "class" : i, value[i]);
11128
      }
11129
11130
      composer.selection.insertNode(image);
11131
      if (wysihtml5.browser.hasProblemsSettingCaretAfterImg()) {
11132
        textNode = doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
11133
        composer.selection.insertNode(textNode);
11134
        composer.selection.setAfter(textNode);
11135
      } else {
11136
        composer.selection.setAfter(image);
11137
      }
11138
    },
11139
11140
    state: function(composer) {
11141
      var doc = composer.doc,
11142
          selectedNode,
11143
          text,
11144
          imagesInSelection;
11145
11146
      if (!wysihtml5.dom.hasElementWithTagName(doc, NODE_NAME)) {
11147
        return false;
11148
      }
11149
11150
      selectedNode = composer.selection.getSelectedNode();
11151
      if (!selectedNode) {
11152
        return false;
11153
      }
11154
11155
      if (selectedNode.nodeName === NODE_NAME) {
11156
        // This works perfectly in IE
11157
        return selectedNode;
11158
      }
11159
11160
      if (selectedNode.nodeType !== wysihtml5.ELEMENT_NODE) {
11161
        return false;
11162
      }
11163
11164
      text = composer.selection.getText();
11165
      text = wysihtml5.lang.string(text).trim();
11166
      if (text) {
11167
        return false;
11168
      }
11169
11170
      imagesInSelection = composer.selection.getNodes(wysihtml5.ELEMENT_NODE, function(node) {
11171
        return node.nodeName === "IMG";
11172
      });
11173
11174
      if (imagesInSelection.length !== 1) {
11175
        return false;
11176
      }
11177
11178
      return imagesInSelection[0];
11179
    }
11180
  };
11181
})(wysihtml5);
11182
;(function(wysihtml5) {
11183
  var LINE_BREAK = "<br>" + (wysihtml5.browser.needsSpaceAfterLineBreak() ? " " : "");
11184
11185
  wysihtml5.commands.insertLineBreak = {
11186
    exec: function(composer, command) {
11187
      if (composer.commands.support(command)) {
11188
        composer.doc.execCommand(command, false, null);
11189
        if (!wysihtml5.browser.autoScrollsToCaret()) {
11190
          composer.selection.scrollIntoView();
11191
        }
11192
      } else {
11193
        composer.commands.exec("insertHTML", LINE_BREAK);
11194
      }
11195
    },
11196
11197
    state: function() {
11198
      return false;
11199
    }
11200
  };
11201
})(wysihtml5);
11202
;wysihtml5.commands.insertOrderedList = {
11203
  exec: function(composer, command) {
11204
    wysihtml5.commands.insertList.exec(composer, command, "OL");
11205
  },
11206
11207
  state: function(composer, command) {
11208
    return wysihtml5.commands.insertList.state(composer, command, "OL");
11209
  }
11210
};
11211
;wysihtml5.commands.insertUnorderedList = {
11212
  exec: function(composer, command) {
11213
    wysihtml5.commands.insertList.exec(composer, command, "UL");
11214
  },
11215
11216
  state: function(composer, command) {
11217
    return wysihtml5.commands.insertList.state(composer, command, "UL");
11218
  }
11219
};
11220
;wysihtml5.commands.insertList = (function(wysihtml5) {
11221
11222
  var isNode = function(node, name) {
11223
    if (node && node.nodeName) {
11224
      if (typeof name === 'string') {
11225
        name = [name];
11226
      }
11227
      for (var n = name.length; n--;) {
11228
        if (node.nodeName === name[n]) {
11229
          return true;
11230
        }
11231
      }
11232
    }
11233
    return false;
11234
  };
11235
11236
  var findListEl = function(node, nodeName, composer) {
11237
    var ret = {
11238
          el: null,
11239
          other: false
11240
        };
11241
11242
    if (node) {
11243
      var parentLi = wysihtml5.dom.getParentElement(node, { nodeName: "LI" }),
11244
          otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11245
11246
      if (isNode(node, nodeName)) {
11247
        ret.el = node;
11248
      } else if (isNode(node, otherNodeName)) {
11249
        ret = {
11250
          el: node,
11251
          other: true
11252
        };
11253
      } else if (parentLi) {
11254
        if (isNode(parentLi.parentNode, nodeName)) {
11255
          ret.el = parentLi.parentNode;
11256
        } else if (isNode(parentLi.parentNode, otherNodeName)) {
11257
          ret = {
11258
            el : parentLi.parentNode,
11259
            other: true
11260
          };
11261
        }
11262
      }
11263
    }
11264
11265
    // do not count list elements outside of composer
11266
    if (ret.el && !composer.element.contains(ret.el)) {
11267
      ret.el = null;
11268
    }
11269
11270
    return ret;
11271
  };
11272
11273
  var handleSameTypeList = function(el, nodeName, composer) {
11274
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
11275
        otherLists, innerLists;
0 ignored issues
show
Unused Code introduced by
The variable otherLists seems to be never used. Consider removing it.
Loading history...
11276
    // Unwrap list
11277
    // <ul><li>foo</li><li>bar</li></ul>
11278
    // becomes:
11279
    // foo<br>bar<br>
11280
    composer.selection.executeAndRestore(function() {
11281
      var otherLists = getListsInSelection(otherNodeName, composer);
11282
      if (otherLists.length) {
11283
        for (var l = otherLists.length; l--;) {
11284
          wysihtml5.dom.renameElement(otherLists[l], nodeName.toLowerCase());
11285
        }
11286
      } else {
11287
        innerLists = getListsInSelection(['OL', 'UL'], composer);
11288
        for (var i = innerLists.length; i--;) {
11289
          wysihtml5.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
11290
        }
11291
        wysihtml5.dom.resolveList(el, composer.config.useLineBreaks);
11292
      }
11293
    });
11294
  };
11295
11296
  var handleOtherTypeList =  function(el, nodeName, composer) {
11297
    var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
11298
    // Turn an ordered list into an unordered list
11299
    // <ol><li>foo</li><li>bar</li></ol>
11300
    // becomes:
11301
    // <ul><li>foo</li><li>bar</li></ul>
11302
    // Also rename other lists in selection
11303
    composer.selection.executeAndRestore(function() {
11304
      var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
11305
11306
      // All selection inner lists get renamed too
11307
      for (var l = renameLists.length; l--;) {
11308
        wysihtml5.dom.renameElement(renameLists[l], nodeName.toLowerCase());
11309
      }
11310
    });
11311
  };
11312
11313
  var getListsInSelection = function(nodeName, composer) {
11314
      var ranges = composer.selection.getOwnRanges(),
11315
          renameLists = [];
11316
11317
      for (var r = ranges.length; r--;) {
11318
        renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
11319
          return isNode(node, nodeName);
11320
        }));
11321
      }
11322
11323
      return renameLists;
11324
  };
11325
11326
  var createListFallback = function(nodeName, composer) {
11327
    // Fallback for Create list
11328
    composer.selection.executeAndRestoreRangy(function() {
11329
      var tempClassName =  "_wysihtml5-temp-" + new Date().getTime(),
11330
          tempElement = composer.selection.deblockAndSurround({
11331
            "nodeName": "div",
11332
            "className": tempClassName
11333
          }),
11334
          isEmpty, list;
11335
11336
      // This space causes new lists to never break on enter 
11337
      var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
11338
      tempElement.innerHTML = tempElement.innerHTML.replace(INVISIBLE_SPACE_REG_EXP, "");
11339
      
11340
      if (tempElement) {
11341
        isEmpty = wysihtml5.lang.array(["", "<br>", wysihtml5.INVISIBLE_SPACE]).contains(tempElement.innerHTML);
11342
        list = wysihtml5.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.uneditableContainerClassname);
11343
        if (isEmpty) {
11344
          composer.selection.selectNode(list.querySelector("li"), true);
11345
        }
11346
      }
11347
    });
11348
  };
11349
11350
  return {
11351
    exec: function(composer, command, nodeName) {
11352
      var doc           = composer.doc,
11353
          cmd           = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
11354
          selectedNode  = composer.selection.getSelectedNode(),
11355
          list          = findListEl(selectedNode, nodeName, composer);
11356
11357
      if (!list.el) {
11358
        if (composer.commands.support(cmd)) {
11359
          doc.execCommand(cmd, false, null);
11360
        } else {
11361
          createListFallback(nodeName, composer);
11362
        }
11363
      } else if (list.other) {
11364
        handleOtherTypeList(list.el, nodeName, composer);
11365
      } else {
11366
        handleSameTypeList(list.el, nodeName, composer);
11367
      }
11368
    },
11369
11370
    state: function(composer, command, nodeName) {
11371
      var selectedNode = composer.selection.getSelectedNode(),
11372
          list         = findListEl(selectedNode, nodeName, composer);
11373
11374
      return (list.el && !list.other) ? list.el : false;
11375
    }
11376
  };
11377
11378
})(wysihtml5);;wysihtml5.commands.italic = {
11379
  exec: function(composer, command) {
11380
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "i");
11381
  },
11382
11383
  state: function(composer, command) {
11384
    // element.ownerDocument.queryCommandState("italic") results:
11385
    // firefox: only <i>
11386
    // chrome:  <i>, <em>, <blockquote>, ...
11387
    // ie:      <i>, <em>
11388
    // opera:   only <i>
11389
    return wysihtml5.commands.formatInline.state(composer, command, "i");
11390
  }
11391
};
11392
;(function(wysihtml5) {
11393
  var CLASS_NAME  = "wysiwyg-text-align-center",
11394
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11395
11396
  wysihtml5.commands.justifyCenter = {
11397
    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...
11398
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11399
    },
11400
11401
    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...
11402
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11403
    }
11404
  };
11405
})(wysihtml5);
11406
;(function(wysihtml5) {
11407
  var CLASS_NAME  = "wysiwyg-text-align-left",
11408
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11409
11410
  wysihtml5.commands.justifyLeft = {
11411
    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...
11412
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11413
    },
11414
11415
    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...
11416
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11417
    }
11418
  };
11419
})(wysihtml5);
11420
;(function(wysihtml5) {
11421
  var CLASS_NAME  = "wysiwyg-text-align-right",
11422
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11423
11424
  wysihtml5.commands.justifyRight = {
11425
    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...
11426
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11427
    },
11428
11429
    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...
11430
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11431
    }
11432
  };
11433
})(wysihtml5);
11434
;(function(wysihtml5) {
11435
  var CLASS_NAME  = "wysiwyg-text-align-justify",
11436
      REG_EXP     = /wysiwyg-text-align-[0-9a-z]+/g;
11437
11438
  wysihtml5.commands.justifyFull = {
11439
    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...
11440
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11441
    },
11442
11443
    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...
11444
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, CLASS_NAME, REG_EXP);
11445
    }
11446
  };
11447
})(wysihtml5);
11448
;(function(wysihtml5) {
11449
  var STYLE_STR  = "text-align: right;",
11450
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11451
11452
  wysihtml5.commands.alignRightStyle = {
11453
    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...
11454
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
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...
11458
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11459
    }
11460
  };
11461
})(wysihtml5);
11462
;(function(wysihtml5) {
11463
  var STYLE_STR  = "text-align: left;",
11464
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11465
11466
  wysihtml5.commands.alignLeftStyle = {
11467
    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...
11468
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11469
    },
11470
11471
    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...
11472
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11473
    }
11474
  };
11475
})(wysihtml5);
11476
;(function(wysihtml5) {
11477
  var STYLE_STR  = "text-align: center;",
11478
      REG_EXP = /(\s|^)text-align\s*:\s*[^;\s]+;?/gi;
11479
11480
  wysihtml5.commands.alignCenterStyle = {
11481
    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...
11482
      return wysihtml5.commands.formatBlock.exec(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11483
    },
11484
11485
    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...
11486
      return wysihtml5.commands.formatBlock.state(composer, "formatBlock", null, null, null, STYLE_STR, REG_EXP);
11487
    }
11488
  };
11489
})(wysihtml5);
11490
;wysihtml5.commands.redo = {
11491
  exec: function(composer) {
11492
    return composer.undoManager.redo();
11493
  },
11494
11495
  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...
11496
    return false;
11497
  }
11498
};
11499
;wysihtml5.commands.underline = {
11500
  exec: function(composer, command) {
11501
    wysihtml5.commands.formatInline.execWithToggle(composer, command, "u");
11502
  },
11503
11504
  state: function(composer, command) {
11505
    return wysihtml5.commands.formatInline.state(composer, command, "u");
11506
  }
11507
};
11508
;wysihtml5.commands.undo = {
11509
  exec: function(composer) {
11510
    return composer.undoManager.undo();
11511
  },
11512
11513
  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...
11514
    return false;
11515
  }
11516
};
11517
;wysihtml5.commands.createTable = {
11518
  exec: function(composer, command, value) {
11519
      var col, row, html;
11520
      if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) {
11521
          if (value.tableStyle) {
11522
            html = "<table style=\"" + value.tableStyle + "\">";
11523
          } else {
11524
            html = "<table>";
11525
          }
11526
          html += "<tbody>";
11527
          for (row = 0; row < value.rows; row ++) {
11528
              html += '<tr>';
11529
              for (col = 0; col < value.cols; col ++) {
11530
                  html += "<td>&nbsp;</td>";
11531
              }
11532
              html += '</tr>';
11533
          }
11534
          html += "</tbody></table>";
11535
          composer.commands.exec("insertHTML", html);
11536
          //composer.selection.insertHTML(html);
11537
      }
11538
11539
11540
  },
11541
11542
  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...
11543
      return false;
11544
  }
11545
};
11546
;wysihtml5.commands.mergeTableCells = {
11547
  exec: function(composer, command) {
11548
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11549
          if (this.state(composer, command)) {
11550
              wysihtml5.dom.table.unmergeCell(composer.tableSelection.start);
11551
          } else {
11552
              wysihtml5.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end);
11553
          }
11554
      }
11555
  },
11556
11557
  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...
11558
      if (composer.tableSelection) {
11559
          var start = composer.tableSelection.start,
11560
              end = composer.tableSelection.end;
11561
          if (start && end && start == end &&
11562
              ((
11563
                  wysihtml5.dom.getAttribute(start, "colspan") &&
11564
                  parseInt(wysihtml5.dom.getAttribute(start, "colspan"), 10) > 1
11565
              ) || (
11566
                  wysihtml5.dom.getAttribute(start, "rowspan") &&
11567
                  parseInt(wysihtml5.dom.getAttribute(start, "rowspan"), 10) > 1
11568
              ))
11569
          ) {
11570
              return [start];
11571
          }
11572
      }
11573
      return false;
11574
  }
11575
};
11576
;wysihtml5.commands.addTableCells = {
11577
  exec: function(composer, command, value) {
11578
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11579
11580
          // switches start and end if start is bigger than end (reverse selection)
11581
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end);
11582
          if (value == "before" || value == "above") {
11583
              wysihtml5.dom.table.addCells(tableSelect.start, value);
11584
          } else if (value == "after" || value == "below") {
11585
              wysihtml5.dom.table.addCells(tableSelect.end, value);
11586
          }
11587
          setTimeout(function() {
11588
              composer.tableSelection.select(tableSelect.start, tableSelect.end);
11589
          },0);
11590
      }
11591
  },
11592
11593
  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...
11594
      return false;
11595
  }
11596
};
11597
;wysihtml5.commands.deleteTableCells = {
11598
  exec: function(composer, command, value) {
11599
      if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) {
11600
          var tableSelect = wysihtml5.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end),
11601
              idx = wysihtml5.dom.table.indexOf(tableSelect.start),
11602
              selCell,
11603
              table = composer.tableSelection.table;
11604
11605
          wysihtml5.dom.table.removeCells(tableSelect.start, value);
11606
          setTimeout(function() {
11607
              // move selection to next or previous if not present
11608
              selCell = wysihtml5.dom.table.findCell(table, idx);
11609
11610
              if (!selCell){
11611
                  if (value == "row") {
11612
                      selCell = wysihtml5.dom.table.findCell(table, {
11613
                          "row": idx.row - 1,
11614
                          "col": idx.col
11615
                      });
11616
                  }
11617
11618
                  if (value == "column") {
11619
                      selCell = wysihtml5.dom.table.findCell(table, {
11620
                          "row": idx.row,
11621
                          "col": idx.col - 1
11622
                      });
11623
                  }
11624
              }
11625
              if (selCell) {
11626
                  composer.tableSelection.select(selCell, selCell);
11627
              }
11628
          }, 0);
11629
11630
      }
11631
  },
11632
11633
  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...
11634
      return false;
11635
  }
11636
};
11637
;wysihtml5.commands.indentList = {
11638
  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...
11639
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11640
    if (listEls) {
11641
      return this.tryToPushLiLevel(listEls, composer.selection);
11642
    }
11643
    return false;
11644
  },
11645
11646
  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...
11647
      return false;
11648
  },
11649
11650
  tryToPushLiLevel: function(liNodes, selection) {
11651
    var listTag, list, prevLi, liNode, prevLiList,
11652
        found = false;
11653
11654
    selection.executeAndRestoreRangy(function() {
11655
11656
      for (var i = liNodes.length; i--;) {
11657
        liNode = liNodes[i];
11658
        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 11657. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11659
        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 11658. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11660
        prevLi = wysihtml5.dom.domNode(liNode).prev({nodeTypes: [wysihtml5.ELEMENT_NODE]});
11661
        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...LEMENT_NODE,false))))}) on line 11660. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11662
11663
        if (prevLi) {
11664
          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 11661. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11665
            prevLiList.appendChild(liNode);
11666
          } else {
11667
            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 11659. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11668
            prevLi.appendChild(list);
11669
          }
11670
          found = true;
11671
        }
11672
      }
11673
11674
    });
11675
    return found;
11676
  }
11677
};
11678
;wysihtml5.commands.outdentList = {
11679
  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...
11680
    var listEls = composer.selection.getSelectionParentsByTag('LI');
11681
    if (listEls) {
11682
      return this.tryToPullLiLevel(listEls, composer);
11683
    }
11684
    return false;
11685
  },
11686
11687
  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...
11688
      return false;
11689
  },
11690
11691
  tryToPullLiLevel: function(liNodes, composer) {
11692
    var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList,
0 ignored issues
show
Unused Code introduced by
The variable list seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable prevLi seems to be never used. Consider removing it.
Loading history...
11693
        found = false,
11694
        that = this;
11695
11696
    composer.selection.executeAndRestoreRangy(function() {
11697
11698
      for (var i = liNodes.length; i--;) {
11699
        liNode = liNodes[i];
11700
        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 11699. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11701
          listNode = liNode.parentNode;
11702
11703
          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 11701. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11704
            found = true;
11705
11706
            outerListNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['OL', 'UL']}, false, composer.element);
11707
            outerLiNode = wysihtml5.dom.getParentElement(listNode.parentNode, { nodeName: ['LI']}, false, composer.element);
11708
11709
            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 11706. 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 11707. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11710
11711
              if (liNode.nextSibling) {
11712
                afterList = that.getAfterList(listNode, liNode);
11713
                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 11712. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
11714
              }
11715
              outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
11716
11717
            } else {
11718
11719
              if (liNode.nextSibling) {
11720
                afterList = that.getAfterList(listNode, liNode);
11721
                liNode.appendChild(afterList);
11722
              }
11723
11724
              for (var j = liNode.childNodes.length; j--;) {
11725
                listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
11726
              }
11727
11728
              listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
11729
              liNode.parentNode.removeChild(liNode);
11730
11731
            }
11732
11733
            // cleanup
11734
            if (listNode.childNodes.length === 0) {
11735
                listNode.parentNode.removeChild(listNode);
11736
            }
11737
          }
11738
        }
11739
      }
11740
11741
    });
11742
    return found;
11743
  },
11744
11745
  getAfterList: function(listNode, liNode) {
11746
    var nodeName = listNode.nodeName,
11747
        newList = document.createElement(nodeName);
11748
11749
    while (liNode.nextSibling) {
11750
      newList.appendChild(liNode.nextSibling);
11751
    }
11752
    return newList;
11753
  }
11754
11755
};;/**
11756
 * Undo Manager for wysihtml5
11757
 * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
11758
 */
11759
(function(wysihtml5) {
11760
  var Z_KEY               = 90,
11761
      Y_KEY               = 89,
11762
      BACKSPACE_KEY       = 8,
11763
      DELETE_KEY          = 46,
11764
      MAX_HISTORY_ENTRIES = 25,
11765
      DATA_ATTR_NODE      = "data-wysihtml5-selection-node",
11766
      DATA_ATTR_OFFSET    = "data-wysihtml5-selection-offset",
11767
      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...
11768
      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...
11769
      dom                 = wysihtml5.dom;
11770
11771
  function cleanTempElements(doc) {
0 ignored issues
show
introduced by
The function cleanTempElements does not seem to be used and can be removed.
Loading history...
11772
    var tempElement;
11773
    while (tempElement = doc.querySelector("._wysihtml5-temp")) {
11774
      tempElement.parentNode.removeChild(tempElement);
11775
    }
11776
  }
11777
11778
  wysihtml5.UndoManager = wysihtml5.lang.Dispatcher.extend(
11779
    /** @scope wysihtml5.UndoManager.prototype */ {
11780
    constructor: function(editor) {
11781
      this.editor = editor;
11782
      this.composer = editor.composer;
11783
      this.element = this.composer.element;
11784
11785
      this.position = 0;
11786
      this.historyStr = [];
11787
      this.historyDom = [];
11788
11789
      this.transact();
11790
11791
      this._observe();
11792
    },
11793
11794
    _observe: function() {
11795
      var that      = this,
11796
          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...
11797
          lastKey;
11798
11799
      // Catch CTRL+Z and CTRL+Y
11800
      dom.observe(this.element, "keydown", function(event) {
11801
        if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
11802
          return;
11803
        }
11804
11805
        var keyCode = event.keyCode,
11806
            isUndo = keyCode === Z_KEY && !event.shiftKey,
11807
            isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
11808
11809
        if (isUndo) {
11810
          that.undo();
11811
          event.preventDefault();
11812
        } else if (isRedo) {
11813
          that.redo();
11814
          event.preventDefault();
11815
        }
11816
      });
11817
11818
      // Catch delete and backspace
11819
      dom.observe(this.element, "keydown", function(event) {
11820
        var keyCode = event.keyCode;
11821
        if (keyCode === lastKey) {
11822
          return;
11823
        }
11824
11825
        lastKey = keyCode;
11826
11827
        if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
11828
          that.transact();
11829
        }
11830
      });
11831
11832
      this.editor
11833
        .on("newword:composer", function() {
11834
          that.transact();
11835
        })
11836
11837
        .on("beforecommand:composer", function() {
11838
          that.transact();
11839
        });
11840
    },
11841
11842
    transact: function() {
11843
      var previousHtml      = this.historyStr[this.position - 1],
11844
          currentHtml       = this.composer.getValue(false, false),
11845
          composerIsVisible   = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
11846
          range, node, offset, element, position;
11847
11848
      if (currentHtml === previousHtml) {
11849
        return;
11850
      }
11851
11852
      var length = this.historyStr.length = this.historyDom.length = this.position;
11853
      if (length > MAX_HISTORY_ENTRIES) {
11854
        this.historyStr.shift();
11855
        this.historyDom.shift();
11856
        this.position--;
11857
      }
11858
11859
      this.position++;
11860
11861
      if (composerIsVisible) {
11862
        // Do not start saving selection if composer is not visible
11863
        range   = this.composer.selection.getRange();
11864
        node    = (range && range.startContainer) ? range.startContainer : this.element;
11865
        offset  = (range && range.startOffset) ? range.startOffset : 0;
11866
11867
        if (node.nodeType === wysihtml5.ELEMENT_NODE) {
11868
          element = node;
11869
        } else {
11870
          element  = node.parentNode;
11871
          position = this.getChildNodeIndex(element, node);
11872
        }
11873
11874
        element.setAttribute(DATA_ATTR_OFFSET, offset);
11875
        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...
11876
          element.setAttribute(DATA_ATTR_NODE, position);
11877
        }
11878
      }
11879
11880
      var clone = this.element.cloneNode(!!currentHtml);
11881
      this.historyDom.push(clone);
11882
      this.historyStr.push(currentHtml);
11883
11884
      if (element) {
11885
        element.removeAttribute(DATA_ATTR_OFFSET);
11886
        element.removeAttribute(DATA_ATTR_NODE);
11887
      }
11888
11889
    },
11890
11891
    undo: function() {
11892
      this.transact();
11893
11894
      if (!this.undoPossible()) {
11895
        return;
11896
      }
11897
11898
      this.set(this.historyDom[--this.position - 1]);
11899
      this.editor.fire("undo:composer");
11900
    },
11901
11902
    redo: function() {
11903
      if (!this.redoPossible()) {
11904
        return;
11905
      }
11906
11907
      this.set(this.historyDom[++this.position - 1]);
11908
      this.editor.fire("redo:composer");
11909
    },
11910
11911
    undoPossible: function() {
11912
      return this.position > 1;
11913
    },
11914
11915
    redoPossible: function() {
11916
      return this.position < this.historyStr.length;
11917
    },
11918
11919
    set: function(historyEntry) {
11920
      this.element.innerHTML = "";
11921
11922
      var i = 0,
11923
          childNodes = historyEntry.childNodes,
11924
          length = historyEntry.childNodes.length;
11925
11926
      for (; i<length; i++) {
11927
        this.element.appendChild(childNodes[i].cloneNode(true));
11928
      }
11929
11930
      // Restore selection
11931
      var offset,
11932
          node,
11933
          position;
11934
11935
      if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
11936
        offset    = historyEntry.getAttribute(DATA_ATTR_OFFSET);
11937
        position  = historyEntry.getAttribute(DATA_ATTR_NODE);
11938
        node      = this.element;
11939
      } else {
11940
        node      = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
11941
        offset    = node.getAttribute(DATA_ATTR_OFFSET);
11942
        position  = node.getAttribute(DATA_ATTR_NODE);
11943
        node.removeAttribute(DATA_ATTR_OFFSET);
11944
        node.removeAttribute(DATA_ATTR_NODE);
11945
      }
11946
11947
      if (position !== null) {
11948
        node = this.getChildNodeByIndex(node, +position);
11949
      }
11950
11951
      this.composer.selection.set(node, offset);
11952
    },
11953
11954
    getChildNodeIndex: function(parent, child) {
11955
      var i           = 0,
11956
          childNodes  = parent.childNodes,
11957
          length      = childNodes.length;
11958
      for (; i<length; i++) {
11959
        if (childNodes[i] === child) {
11960
          return i;
11961
        }
11962
      }
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...
11963
    },
11964
11965
    getChildNodeByIndex: function(parent, index) {
11966
      return parent.childNodes[index];
11967
    }
11968
  });
11969
})(wysihtml5);
11970
;/**
11971
 * TODO: the following methods still need unit test coverage
11972
 */
11973
wysihtml5.views.View = Base.extend(
11974
  /** @scope wysihtml5.views.View.prototype */ {
11975
  constructor: function(parent, textareaElement, config) {
11976
    this.parent   = parent;
11977
    this.element  = textareaElement;
11978
    this.config   = config;
11979
    if (!this.config.noTextarea) {
11980
        this._observeViewChange();
11981
    }
11982
  },
11983
11984
  _observeViewChange: function() {
11985
    var that = this;
11986
    this.parent.on("beforeload", function() {
11987
      that.parent.on("change_view", function(view) {
11988
        if (view === that.name) {
11989
          that.parent.currentView = that;
11990
          that.show();
11991
          // Using tiny delay here to make sure that the placeholder is set before focusing
11992
          setTimeout(function() { that.focus(); }, 0);
11993
        } else {
11994
          that.hide();
11995
        }
11996
      });
11997
    });
11998
  },
11999
12000
  focus: function() {
12001
    if (this.element.ownerDocument.querySelector(":focus") === this.element) {
12002
      return;
12003
    }
12004
12005
    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...
12006
  },
12007
12008
  hide: function() {
12009
    this.element.style.display = "none";
12010
  },
12011
12012
  show: function() {
12013
    this.element.style.display = "";
12014
  },
12015
12016
  disable: function() {
12017
    this.element.setAttribute("disabled", "disabled");
12018
  },
12019
12020
  enable: function() {
12021
    this.element.removeAttribute("disabled");
12022
  }
12023
});
12024
;(function(wysihtml5) {
12025
  var dom       = wysihtml5.dom,
12026
      browser   = wysihtml5.browser;
12027
12028
  wysihtml5.views.Composer = wysihtml5.views.View.extend(
12029
    /** @scope wysihtml5.views.Composer.prototype */ {
12030
    name: "composer",
12031
12032
    // Needed for firefox in order to display a proper caret in an empty contentEditable
12033
    CARET_HACK: "<br>",
12034
12035
    constructor: function(parent, editableElement, config) {
12036
      this.base(parent, editableElement, config);
12037
      if (!this.config.noTextarea) {
12038
          this.textarea = this.parent.textarea;
12039
      } else {
12040
          this.editableArea = editableElement;
12041
      }
12042
      if (this.config.contentEditableMode) {
12043
          this._initContentEditableArea();
12044
      } else {
12045
          this._initSandbox();
12046
      }
12047
    },
12048
12049
    clear: function() {
12050
      this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : this.CARET_HACK;
12051
    },
12052
12053
    getValue: function(parse, clearInternals) {
12054
      var value = this.isEmpty() ? "" : wysihtml5.quirks.getCorrectInnerHTML(this.element);
12055
      if (parse !== false) {
12056
        value = this.parent.parse(value, (clearInternals === false) ? false : true);
12057
      }
12058
12059
      return value;
12060
    },
12061
12062
    setValue: function(html, parse) {
12063
      if (parse) {
12064
        html = this.parent.parse(html);
12065
      }
12066
12067
      try {
12068
        this.element.innerHTML = html;
12069
      } catch (e) {
12070
        this.element.innerText = html;
12071
      }
12072
    },
12073
12074
    cleanUp: function() {
12075
        this.parent.parse(this.element);
12076
    },
12077
12078
    show: function() {
12079
      this.editableArea.style.display = this._displayStyle || "";
12080
12081
      if (!this.config.noTextarea && !this.textarea.element.disabled) {
12082
        // Firefox needs this, otherwise contentEditable becomes uneditable
12083
        this.disable();
12084
        this.enable();
12085
      }
12086
    },
12087
12088
    hide: function() {
12089
      this._displayStyle = dom.getStyle("display").from(this.editableArea);
12090
      if (this._displayStyle === "none") {
12091
        this._displayStyle = null;
12092
      }
12093
      this.editableArea.style.display = "none";
12094
    },
12095
12096
    disable: function() {
12097
      this.parent.fire("disable:composer");
12098
      this.element.removeAttribute("contentEditable");
12099
    },
12100
12101
    enable: function() {
12102
      this.parent.fire("enable:composer");
12103
      this.element.setAttribute("contentEditable", "true");
12104
    },
12105
12106
    focus: function(setToEnd) {
12107
      // IE 8 fires the focus event after .focus()
12108
      // This is needed by our simulate_placeholder.js to work
12109
      // therefore we clear it ourselves this time
12110
      if (wysihtml5.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
12111
        this.clear();
12112
      }
12113
12114
      this.base();
12115
12116
      var lastChild = this.element.lastChild;
12117
      if (setToEnd && lastChild && this.selection) {
12118
        if (lastChild.nodeName === "BR") {
12119
          this.selection.setBefore(this.element.lastChild);
12120
        } else {
12121
          this.selection.setAfter(this.element.lastChild);
12122
        }
12123
      }
12124
    },
12125
12126
    getTextContent: function() {
12127
      return dom.getTextContent(this.element);
12128
    },
12129
12130
    hasPlaceholderSet: function() {
12131
      return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
12132
    },
12133
12134
    isEmpty: function() {
12135
      var innerHTML = this.element.innerHTML.toLowerCase();
12136
      return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML)  ||
12137
             innerHTML === ""            ||
12138
             innerHTML === "<br>"        ||
12139
             innerHTML === "<p></p>"     ||
12140
             innerHTML === "<p><br></p>" ||
12141
             this.hasPlaceholderSet();
12142
    },
12143
12144
    _initContentEditableArea: function() {
12145
        var that = this;
12146
12147
        if (this.config.noTextarea) {
12148
            this.sandbox = new dom.ContentEditableArea(function() {
12149
                that._create();
12150
            }, {}, this.editableArea);
12151
        } else {
12152
            this.sandbox = new dom.ContentEditableArea(function() {
12153
                that._create();
12154
            });
12155
            this.editableArea = this.sandbox.getContentEditable();
12156
            dom.insert(this.editableArea).after(this.textarea.element);
12157
            this._createWysiwygFormField();
12158
        }
12159
    },
12160
12161
    _initSandbox: function() {
12162
      var that = this;
12163
12164
      this.sandbox = new dom.Sandbox(function() {
12165
        that._create();
12166
      }, {
12167
        stylesheets:  this.config.stylesheets
12168
      });
12169
      this.editableArea  = this.sandbox.getIframe();
12170
12171
      var textareaElement = this.textarea.element;
12172
      dom.insert(this.editableArea).after(textareaElement);
12173
12174
      this._createWysiwygFormField();
12175
    },
12176
12177
    // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
12178
    _createWysiwygFormField: function() {
12179
        if (this.textarea.element.form) {
12180
          var hiddenField = document.createElement("input");
12181
          hiddenField.type   = "hidden";
12182
          hiddenField.name   = "_wysihtml5_mode";
12183
          hiddenField.value  = 1;
12184
          dom.insert(hiddenField).after(this.textarea.element);
12185
        }
12186
    },
12187
12188
    _create: function() {
12189
      var that = this;
12190
      this.doc                = this.sandbox.getDocument();
12191
      this.element            = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
12192
      if (!this.config.noTextarea) {
12193
          this.textarea           = this.parent.textarea;
12194
          this.element.innerHTML  = this.textarea.getValue(true, false);
12195
      } else {
12196
          this.cleanUp(); // cleans contenteditable on initiation as it may contain html
12197
      }
12198
12199
      // Make sure our selection handler is ready
12200
      this.selection = new wysihtml5.Selection(this.parent, this.element, this.config.uneditableContainerClassname);
12201
12202
      // Make sure commands dispatcher is ready
12203
      this.commands  = new wysihtml5.Commands(this.parent);
12204
12205
      if (!this.config.noTextarea) {
12206
          dom.copyAttributes([
12207
              "className", "spellcheck", "title", "lang", "dir", "accessKey"
12208
          ]).from(this.textarea.element).to(this.element);
12209
      }
12210
12211
      dom.addClass(this.element, this.config.composerClassName);
12212
      //
12213
      // Make the editor look like the original textarea, by syncing styles
12214
      if (this.config.style && !this.config.contentEditableMode) {
12215
        this.style();
12216
      }
12217
12218
      this.observe();
12219
12220
      var name = this.config.name;
12221
      if (name) {
12222
        dom.addClass(this.element, name);
12223
        if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
12224
      }
12225
12226
      this.enable();
12227
12228
      if (!this.config.noTextarea && this.textarea.element.disabled) {
12229
        this.disable();
12230
      }
12231
12232
      // Simulate html5 placeholder attribute on contentEditable element
12233
      var placeholderText = typeof(this.config.placeholder) === "string"
12234
        ? this.config.placeholder
12235
        : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
12236
      if (placeholderText) {
12237
        dom.simulatePlaceholder(this.parent, this, placeholderText);
12238
      }
12239
12240
      // Make sure that the browser avoids using inline styles whenever possible
12241
      this.commands.exec("styleWithCSS", false);
12242
12243
      this._initAutoLinking();
12244
      this._initObjectResizing();
12245
      this._initUndoManager();
12246
      this._initLineBreaking();
12247
12248
      // Simulate html5 autofocus on contentEditable element
12249
      // This doesn't work on IOS (5.1.1)
12250
      if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
12251
        setTimeout(function() { that.focus(true); }, 100);
12252
      }
12253
12254
      // IE sometimes leaves a single paragraph, which can't be removed by the user
12255
      if (!browser.clearsContentEditableCorrectly()) {
12256
        wysihtml5.quirks.ensureProperClearing(this);
12257
      }
12258
12259
      // Set up a sync that makes sure that textarea and editor have the same content
12260
      if (this.initSync && this.config.sync) {
12261
        this.initSync();
12262
      }
12263
12264
      // Okay hide the textarea, we are ready to go
12265
      if (!this.config.noTextarea) { this.textarea.hide(); }
12266
12267
      // Fire global (before-)load event
12268
      this.parent.fire("beforeload").fire("load");
12269
    },
12270
12271
    _initAutoLinking: function() {
12272
      var that                           = this,
12273
          supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
12274
          supportsAutoLinking            = browser.doesAutoLinkingInContentEditable();
12275
      if (supportsDisablingOfAutoLinking) {
12276
        this.commands.exec("autoUrlDetect", false);
12277
      }
12278
12279
      if (!this.config.autoLink) {
12280
        return;
12281
      }
12282
12283
      // Only do the auto linking by ourselves when the browser doesn't support auto linking
12284
      // OR when he supports auto linking but we were able to turn it off (IE9+)
12285
      if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
12286
        this.parent.on("newword:composer", function() {
12287
          if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
12288
            that.selection.executeAndRestore(function(startContainer, endContainer) {
12289
              var uneditables = that.element.querySelectorAll("." + that.config.uneditableContainerClassname),
12290
                  isInUneditable = false;
12291
12292
              for (var i = uneditables.length; i--;) {
12293
                if (wysihtml5.dom.contains(uneditables[i], endContainer)) {
12294
                  isInUneditable = true;
12295
                }
12296
              }
12297
12298
              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...
12299
            });
12300
          }
12301
        });
12302
12303
        dom.observe(this.element, "blur", function() {
12304
          dom.autoLink(that.element, [that.config.uneditableContainerClassname]);
12305
        });
12306
      }
12307
12308
      // Assuming we have the following:
12309
      //  <a href="http://www.google.de">http://www.google.de</a>
12310
      // If a user now changes the url in the innerHTML we want to make sure that
12311
      // it's synchronized with the href attribute (as long as the innerHTML is still a url)
12312
      var // Use a live NodeList to check whether there are any links in the document
12313
          links           = this.sandbox.getDocument().getElementsByTagName("a"),
12314
          // The autoLink helper method reveals a reg exp to detect correct urls
12315
          urlRegExp       = dom.autoLink.URL_REG_EXP,
12316
          getTextContent  = function(element) {
12317
            var textContent = wysihtml5.lang.string(dom.getTextContent(element)).trim();
12318
            if (textContent.substr(0, 4) === "www.") {
12319
              textContent = "http://" + textContent;
12320
            }
12321
            return textContent;
12322
          };
12323
12324
      dom.observe(this.element, "keydown", function(event) {
12325
        if (!links.length) {
12326
          return;
12327
        }
12328
12329
        var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
12330
            link         = dom.getParentElement(selectedNode, { nodeName: "A" }, 4),
12331
            textContent;
12332
12333
        if (!link) {
12334
          return;
12335
        }
12336
12337
        textContent = getTextContent(link);
12338
        // keydown is fired before the actual content is changed
12339
        // therefore we set a timeout to change the href
12340
        setTimeout(function() {
12341
          var newTextContent = getTextContent(link);
12342
          if (newTextContent === textContent) {
12343
            return;
12344
          }
12345
12346
          // Only set href when new href looks like a valid url
12347
          if (newTextContent.match(urlRegExp)) {
12348
            link.setAttribute("href", newTextContent);
12349
          }
12350
        }, 0);
12351
      });
12352
    },
12353
12354
    _initObjectResizing: function() {
12355
      this.commands.exec("enableObjectResizing", true);
12356
12357
      // IE sets inline styles after resizing objects
12358
      // The following lines make sure that the width/height css properties
12359
      // are copied over to the width/height attributes
12360
      if (browser.supportsEvent("resizeend")) {
12361
        var properties        = ["width", "height"],
12362
            propertiesLength  = properties.length,
12363
            element           = this.element;
12364
12365
        dom.observe(element, "resizeend", function(event) {
12366
          var target = event.target || event.srcElement,
12367
              style  = target.style,
12368
              i      = 0,
12369
              property;
12370
12371
          if (target.nodeName !== "IMG") {
12372
            return;
12373
          }
12374
12375
          for (; i<propertiesLength; i++) {
12376
            property = properties[i];
12377
            if (style[property]) {
12378
              target.setAttribute(property, parseInt(style[property], 10));
12379
              style[property] = "";
12380
            }
12381
          }
12382
12383
          // After resizing IE sometimes forgets to remove the old resize handles
12384
          wysihtml5.quirks.redraw(element);
12385
        });
12386
      }
12387
    },
12388
12389
    _initUndoManager: function() {
12390
      this.undoManager = new wysihtml5.UndoManager(this.parent);
12391
    },
12392
12393
    _initLineBreaking: function() {
12394
      var that                              = this,
12395
          USE_NATIVE_LINE_BREAK_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
12396
          LIST_TAGS                         = ["UL", "OL", "MENU"];
12397
12398
      function adjust(selectedNode) {
12399
        var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
12400
        if (parentElement && dom.contains(that.element, parentElement)) {
12401
          that.selection.executeAndRestore(function() {
12402
            if (that.config.useLineBreaks) {
12403
              dom.replaceWithChildNodes(parentElement);
12404
            } else if (parentElement.nodeName !== "P") {
12405
              dom.renameElement(parentElement, "p");
12406
            }
12407
          });
12408
        }
12409
      }
12410
12411
      if (!this.config.useLineBreaks) {
12412
        dom.observe(this.element, ["focus", "keydown"], function() {
12413
          if (that.isEmpty()) {
12414
            var paragraph = that.doc.createElement("P");
12415
            that.element.innerHTML = "";
12416
            that.element.appendChild(paragraph);
12417
            if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
12418
              paragraph.innerHTML = "<br>";
12419
              that.selection.setBefore(paragraph.firstChild);
12420
            } else {
12421
              that.selection.selectNode(paragraph, true);
12422
            }
12423
          }
12424
        });
12425
      }
12426
12427
      // Under certain circumstances Chrome + Safari create nested <p> or <hX> tags after paste
12428
      // Inserting an invisible white space in front of it fixes the issue
12429
      // This is too hacky and causes selection not to replace content on paste in chrome
12430
     /* if (browser.createsNestedInvalidMarkupAfterPaste()) {
12431
        dom.observe(this.element, "paste", function(event) {
12432
          var invisibleSpace = that.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
12433
          that.selection.insertNode(invisibleSpace);
12434
        });
12435
      }*/
12436
12437
12438
      dom.observe(this.element, "keydown", function(event) {
12439
        var keyCode = event.keyCode;
12440
12441
        if (event.shiftKey) {
12442
          return;
12443
        }
12444
12445
        if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
12446
          return;
12447
        }
12448
        var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { nodeName: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
12449
        if (blockElement) {
12450
          setTimeout(function() {
12451
            // Unwrap paragraph after leaving a list or a H1-6
12452
            var selectedNode = that.selection.getSelectedNode(),
12453
                list;
12454
12455
            if (blockElement.nodeName === "LI") {
12456
              if (!selectedNode) {
12457
                return;
12458
              }
12459
12460
              list = dom.getParentElement(selectedNode, { nodeName: LIST_TAGS }, 2);
12461
12462
              if (!list) {
12463
                adjust(selectedNode);
12464
              }
12465
            }
12466
12467
            if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
12468
              adjust(selectedNode);
12469
            }
12470
          }, 0);
12471
          return;
12472
        }
12473
12474
        if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
12475
          event.preventDefault();
12476
          that.commands.exec("insertLineBreak");
12477
12478
        }
12479
      });
12480
    }
12481
  });
12482
})(wysihtml5);
12483
;(function(wysihtml5) {
12484
  var dom             = wysihtml5.dom,
12485
      doc             = document,
12486
      win             = window,
12487
      HOST_TEMPLATE   = doc.createElement("div"),
12488
      /**
12489
       * Styles to copy from textarea to the composer element
12490
       */
12491
      TEXT_FORMATTING = [
12492
        "background-color",
12493
        "color", "cursor",
12494
        "font-family", "font-size", "font-style", "font-variant", "font-weight",
12495
        "line-height", "letter-spacing",
12496
        "text-align", "text-decoration", "text-indent", "text-rendering",
12497
        "word-break", "word-wrap", "word-spacing"
12498
      ],
12499
      /**
12500
       * Styles to copy from textarea to the iframe
12501
       */
12502
      BOX_FORMATTING = [
12503
        "background-color",
12504
        "border-collapse",
12505
        "border-bottom-color", "border-bottom-style", "border-bottom-width",
12506
        "border-left-color", "border-left-style", "border-left-width",
12507
        "border-right-color", "border-right-style", "border-right-width",
12508
        "border-top-color", "border-top-style", "border-top-width",
12509
        "clear", "display", "float",
12510
        "margin-bottom", "margin-left", "margin-right", "margin-top",
12511
        "outline-color", "outline-offset", "outline-width", "outline-style",
12512
        "padding-left", "padding-right", "padding-top", "padding-bottom",
12513
        "position", "top", "left", "right", "bottom", "z-index",
12514
        "vertical-align", "text-align",
12515
        "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
12516
        "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
12517
        "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
12518
        "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
12519
        "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
12520
        "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
12521
        "width", "height"
12522
      ],
12523
      ADDITIONAL_CSS_RULES = [
12524
        "html                 { height: 100%; }",
12525
        "body                 { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
12526
        "body > p:first-child { margin-top: 0; }",
12527
        "._wysihtml5-temp     { display: none; }",
12528
        wysihtml5.browser.isGecko ?
12529
          "body.placeholder { color: graytext !important; }" :
12530
          "body.placeholder { color: #a9a9a9 !important; }",
12531
        // Ensure that user see's broken images and can delete them
12532
        "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
12533
      ];
12534
12535
  /**
12536
   * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
12537
   * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
12538
   *
12539
   * Other browsers need a more hacky way: (pssst don't tell my mama)
12540
   * In order to prevent the element being scrolled into view when focusing it, we simply
12541
   * move it out of the scrollable area, focus it, and reset it's position
12542
   */
12543
  var focusWithoutScrolling = function(element) {
12544
    if (element.setActive) {
12545
      // Following line could cause a js error when the textarea is invisible
12546
      // See https://github.com/xing/wysihtml5/issues/9
12547
      try { element.setActive(); } catch(e) {}
12548
    } else {
12549
      var elementStyle = element.style,
12550
          originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
12551
          originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
12552
          originalStyles = {
12553
            position:         elementStyle.position,
12554
            top:              elementStyle.top,
12555
            left:             elementStyle.left,
12556
            WebkitUserSelect: elementStyle.WebkitUserSelect
12557
          };
12558
12559
      dom.setStyles({
12560
        position:         "absolute",
12561
        top:              "-99999px",
12562
        left:             "-99999px",
12563
        // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
12564
        WebkitUserSelect: "none"
12565
      }).on(element);
12566
12567
      element.focus();
12568
12569
      dom.setStyles(originalStyles).on(element);
12570
12571
      if (win.scrollTo) {
12572
        // Some browser extensions unset this method to prevent annoyances
12573
        // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
12574
        // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
12575
        win.scrollTo(originalScrollLeft, originalScrollTop);
12576
      }
12577
    }
12578
  };
12579
12580
12581
  wysihtml5.views.Composer.prototype.style = function() {
12582
    var that                  = this,
12583
        originalActiveElement = doc.querySelector(":focus"),
12584
        textareaElement       = this.textarea.element,
12585
        hasPlaceholder        = textareaElement.hasAttribute("placeholder"),
12586
        originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder"),
12587
        originalDisplayValue  = textareaElement.style.display,
12588
        originalDisabled      = textareaElement.disabled,
12589
        displayValueForCopying;
12590
12591
    this.focusStylesHost      = HOST_TEMPLATE.cloneNode(false);
12592
    this.blurStylesHost       = HOST_TEMPLATE.cloneNode(false);
12593
    this.disabledStylesHost   = HOST_TEMPLATE.cloneNode(false);
12594
12595
    // Remove placeholder before copying (as the placeholder has an affect on the computed style)
12596
    if (hasPlaceholder) {
12597
      textareaElement.removeAttribute("placeholder");
12598
    }
12599
12600
    if (textareaElement === originalActiveElement) {
12601
      textareaElement.blur();
12602
    }
12603
12604
    // enable for copying styles
12605
    textareaElement.disabled = false;
12606
12607
    // set textarea to display="none" to get cascaded styles via getComputedStyle
12608
    textareaElement.style.display = displayValueForCopying = "none";
12609
12610
    if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
12611
        (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
12612
      textareaElement.style.display = displayValueForCopying = originalDisplayValue;
12613
    }
12614
12615
    // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
12616
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
12617
12618
    // --------- editor styles ---------
12619
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
12620
12621
    // --------- apply standard rules ---------
12622
    dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
12623
12624
    // --------- :disabled styles ---------
12625
    textareaElement.disabled = true;
12626
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12627
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
12628
    textareaElement.disabled = originalDisabled;
12629
12630
    // --------- :focus styles ---------
12631
    textareaElement.style.display = originalDisplayValue;
12632
    focusWithoutScrolling(textareaElement);
12633
    textareaElement.style.display = displayValueForCopying;
12634
12635
    dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12636
    dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
12637
12638
    // reset textarea
12639
    textareaElement.style.display = originalDisplayValue;
12640
12641
    dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
12642
12643
    // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
12644
    // this is needed for when the change_view event is fired where the iframe is hidden and then
12645
    // the blur event fires and re-displays it
12646
    var boxFormattingStyles = wysihtml5.lang.array(BOX_FORMATTING).without(["display"]);
12647
12648
    // --------- restore focus ---------
12649
    if (originalActiveElement) {
12650
      originalActiveElement.focus();
12651
    } else {
12652
      textareaElement.blur();
12653
    }
12654
12655
    // --------- restore placeholder ---------
12656
    if (hasPlaceholder) {
12657
      textareaElement.setAttribute("placeholder", originalPlaceholder);
12658
    }
12659
12660
    // --------- Sync focus/blur styles ---------
12661
    this.parent.on("focus:composer", function() {
12662
      dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
12663
      dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
12664
    });
12665
12666
    this.parent.on("blur:composer", function() {
12667
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12668
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12669
    });
12670
12671
    this.parent.observe("disable:composer", function() {
12672
      dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
12673
      dom.copyStyles(TEXT_FORMATTING)     .from(that.disabledStylesHost).to(that.element);
12674
    });
12675
12676
    this.parent.observe("enable:composer", function() {
12677
      dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
12678
      dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
12679
    });
12680
12681
    return this;
12682
  };
12683
})(wysihtml5);
12684
;/**
12685
 * Taking care of events
12686
 *  - Simulating 'change' event on contentEditable element
12687
 *  - Handling drag & drop logic
12688
 *  - Catch paste events
12689
 *  - Dispatch proprietary newword:composer event
12690
 *  - Keyboard shortcuts
12691
 */
12692
(function(wysihtml5) {
12693
  var dom       = wysihtml5.dom,
12694
      browser   = wysihtml5.browser,
12695
      /**
12696
       * Map keyCodes to query commands
12697
       */
12698
      shortcuts = {
12699
        "66": "bold",     // B
12700
        "73": "italic",   // I
12701
        "85": "underline" // U
12702
      };
12703
12704
  var deleteAroundEditable = function(selection, uneditable, element) {
12705
    // merge node with previous node from uneditable
12706
    var prevNode = selection.getPreviousNode(uneditable, true),
12707
        curNode = selection.getSelectedNode();
12708
12709
    if (curNode.nodeType !== 1 && curNode.parentNode !== element) { curNode = curNode.parentNode; }
12710
    if (prevNode) {
12711
      if (curNode.nodeType == 1) {
12712
        var first = curNode.firstChild;
12713
12714
        if (prevNode.nodeType == 1) {
12715
          while (curNode.firstChild) {
12716
            prevNode.appendChild(curNode.firstChild);
12717
          }
12718
        } else {
12719
          while (curNode.firstChild) {
12720
            uneditable.parentNode.insertBefore(curNode.firstChild, uneditable);
12721
          }
12722
        }
12723
        if (curNode.parentNode) {
12724
          curNode.parentNode.removeChild(curNode);
12725
        }
12726
        selection.setBefore(first);
12727
      } else {
12728
        if (prevNode.nodeType == 1) {
12729
          prevNode.appendChild(curNode);
12730
        } else {
12731
          uneditable.parentNode.insertBefore(curNode, uneditable);
12732
        }
12733
        selection.setBefore(curNode);
12734
      }
12735
    }
12736
  };
12737
12738
  var handleDeleteKeyPress = function(event, selection, element, composer) {
12739
    if (selection.isCollapsed()) {
12740
      if (selection.caretIsInTheBeginnig('LI')) {
12741
        event.preventDefault();
12742
        composer.commands.exec('outdentList');
12743
      } else if (selection.caretIsInTheBeginnig()) {
12744
        event.preventDefault();
12745
      } else {
12746
12747
        if (selection.caretIsFirstInSelection() &&
12748
            selection.getPreviousNode() &&
12749
            selection.getPreviousNode().nodeName &&
12750
            (/^H\d$/gi).test(selection.getPreviousNode().nodeName)
12751
        ) {
12752
          var prevNode = selection.getPreviousNode();
12753
          event.preventDefault();
12754
          if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
12755
            // heading is empty
12756
            prevNode.parentNode.removeChild(prevNode);
12757
          } else {
12758
            var range = prevNode.ownerDocument.createRange();
12759
            range.selectNodeContents(prevNode);
12760
            range.collapse(false);
12761
            selection.setSelection(range);
12762
          }
12763
        }
12764
12765
        var beforeUneditable = selection.caretIsBeforeUneditable();
12766
        // Do a special delete if caret would delete uneditable
12767
        if (beforeUneditable) {
12768
          event.preventDefault();
12769
          deleteAroundEditable(selection, beforeUneditable, element);
12770
        }
12771
      }
12772
    } else {
12773
      if (selection.containsUneditable()) {
12774
        event.preventDefault();
12775
        selection.deleteContents();
12776
      }
12777
    }
12778
  };
12779
12780
  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...
12781
    if (!composer.selection.isCollapsed()) {
12782
      composer.selection.deleteContents();
12783
    } else if (composer.selection.caretIsInTheBeginnig('LI')) {
12784
      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...
12785
    }
12786
12787
    // Is &emsp; close enough to tab. Could not find enough counter arguments for now.
12788
    composer.commands.exec("insertHTML", "&emsp;");
12789
  };
12790
12791
  wysihtml5.views.Composer.prototype.observe = function() {
12792
    var that                = this,
12793
        state               = this.getValue(false, false),
12794
        container           = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
12795
        element             = this.element,
12796
        focusBlurElement    = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? element : this.sandbox.getWindow(),
12797
        pasteEvents         = ["drop", "paste", "beforepaste"],
12798
        interactionEvents   = ["drop", "paste", "mouseup", "focus", "keyup"];
12799
12800
    // --------- destroy:composer event ---------
12801
    dom.observe(container, "DOMNodeRemoved", function() {
12802
      clearInterval(domNodeRemovedInterval);
12803
      that.parent.fire("destroy:composer");
12804
    });
12805
12806
    // DOMNodeRemoved event is not supported in IE 8
12807
    if (!browser.supportsMutationEvents()) {
12808
        var domNodeRemovedInterval = setInterval(function() {
12809
          if (!dom.contains(document.documentElement, container)) {
12810
            clearInterval(domNodeRemovedInterval);
12811
            that.parent.fire("destroy:composer");
12812
          }
12813
        }, 250);
12814
    }
12815
12816
    // --------- User interaction tracking --
12817
12818
    dom.observe(focusBlurElement, interactionEvents, function() {
12819
      setTimeout(function() {
12820
        that.parent.fire("interaction").fire("interaction:composer");
12821
      }, 0);
12822
    });
12823
12824
12825
    if (this.config.handleTables) {
12826
      if(!this.tableClickHandle && this.doc.execCommand && wysihtml5.browser.supportsCommand(this.doc, "enableObjectResizing") && wysihtml5.browser.supportsCommand(this.doc, "enableInlineTableEditing")) {
12827
        if (this.sandbox.getIframe) {
12828
          this.tableClickHandle = dom.observe(container , ["focus", "mouseup", "mouseover"], function() {
12829
            that.doc.execCommand("enableObjectResizing", false, "false");
12830
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12831
            that.tableClickHandle.stop();
12832
          });
12833
        } else {
12834
          setTimeout(function() {
12835
            that.doc.execCommand("enableObjectResizing", false, "false");
12836
            that.doc.execCommand("enableInlineTableEditing", false, "false");
12837
          }, 0);
12838
        }
12839
      }
12840
      this.tableSelection = wysihtml5.quirks.tableCellsSelection(element, that.parent);
12841
    }
12842
12843
    // --------- Focus & blur logic ---------
12844
    dom.observe(focusBlurElement, "focus", function(event) {
12845
      that.parent.fire("focus", event).fire("focus:composer", event);
12846
12847
      // Delay storing of state until all focus handler are fired
12848
      // especially the one which resets the placeholder
12849
      setTimeout(function() { state = that.getValue(false, false); }, 0);
12850
    });
12851
12852
    dom.observe(focusBlurElement, "blur", function(event) {
12853
      if (state !== that.getValue(false, false)) {
12854
        //create change event if supported (all except IE8)
12855
        var changeevent = event;
12856
        if(typeof Object.create == 'function') {
12857
          changeevent = Object.create(event, { type: { value: 'change' } });
12858
        }
12859
        that.parent.fire("change", changeevent).fire("change:composer", changeevent);
12860
      }
12861
      that.parent.fire("blur", event).fire("blur:composer", event);
12862
    });
12863
12864
    // --------- Drag & Drop logic ---------
12865
    dom.observe(element, "dragenter", function() {
12866
      that.parent.fire("unset_placeholder");
12867
    });
12868
12869
    dom.observe(element, pasteEvents, function(event) {
12870
      that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12871
    });
12872
12873
12874
    if (this.config.copyedFromMarking) {
12875
      // If supported the copied source is based directly on selection
12876
      // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
12877
      dom.observe(element, "copy", function(event) {
12878
        if (event.clipboardData) {
12879
          event.clipboardData.setData("text/html", that.config.copyedFromMarking + that.selection.getHtml());
12880
          event.preventDefault();
12881
        }
12882
        that.parent.fire(event.type, event).fire(event.type + ":composer", event);
12883
      });
12884
    }
12885
12886
    // --------- neword event ---------
12887
    dom.observe(element, "keyup", function(event) {
12888
      var keyCode = event.keyCode;
12889
      if (keyCode === wysihtml5.SPACE_KEY || keyCode === wysihtml5.ENTER_KEY) {
12890
        that.parent.fire("newword:composer");
12891
      }
12892
    });
12893
12894
    this.parent.on("paste:composer", function() {
12895
      setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
12896
    });
12897
12898
    // --------- Make sure that images are selected when clicking on them ---------
12899
    if (!browser.canSelectImagesInContentEditable()) {
12900
      dom.observe(element, "mousedown", function(event) {
12901
        var target = event.target;
12902
        var allImages = element.querySelectorAll('img'),
12903
            notMyImages = element.querySelectorAll('.' + that.config.uneditableContainerClassname + ' img'),
12904
            myImages = wysihtml5.lang.array(allImages).without(notMyImages);
12905
12906
        if (target.nodeName === "IMG" && wysihtml5.lang.array(myImages).contains(target)) {
12907
          that.selection.selectNode(target);
12908
        }
12909
      });
12910
    }
12911
12912
    if (!browser.canSelectImagesInContentEditable()) {
12913
        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...
12914
            // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
12915
            setTimeout(function() {
12916
                that.selection.getSelection().removeAllRanges();
12917
            }, 0);
12918
        });
12919
    }
12920
12921
    if (browser.hasHistoryIssue() && browser.supportsSelectionModify()) {
12922
      dom.observe(element, "keydown", function(event) {
12923
        if (!event.metaKey && !event.ctrlKey) {
12924
          return;
12925
        }
12926
12927
        var keyCode   = event.keyCode,
12928
            win       = element.ownerDocument.defaultView,
12929
            selection = win.getSelection();
12930
12931
        if (keyCode === 37 || keyCode === 39) {
12932
          if (keyCode === 37) {
12933
            selection.modify("extend", "left", "lineboundary");
12934
            if (!event.shiftKey) {
12935
              selection.collapseToStart();
12936
            }
12937
          }
12938
          if (keyCode === 39) {
12939
            selection.modify("extend", "right", "lineboundary");
12940
            if (!event.shiftKey) {
12941
              selection.collapseToEnd();
12942
            }
12943
          }
12944
          event.preventDefault();
12945
        }
12946
      });
12947
    }
12948
12949
    // --------- Shortcut logic ---------
12950
    dom.observe(element, "keydown", function(event) {
12951
      var keyCode  = event.keyCode,
12952
          command  = shortcuts[keyCode];
12953
      if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
12954
        that.commands.exec(command);
12955
        event.preventDefault();
12956
      }
12957
      if (keyCode === 8) {
12958
        // delete key
12959
        handleDeleteKeyPress(event, that.selection, element, that);
12960
      } else if (that.config.handleTabKey && keyCode === 9) {
12961
        event.preventDefault();
12962
        handleTabKeyDown(that, element);
12963
      }
12964
    });
12965
12966
    // --------- Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor ---------
12967
    dom.observe(element, "keydown", function(event) {
12968
      var target  = that.selection.getSelectedNode(true),
12969
          keyCode = event.keyCode,
12970
          parent;
12971
      if (target && target.nodeName === "IMG" && (keyCode === wysihtml5.BACKSPACE_KEY || keyCode === wysihtml5.DELETE_KEY)) { // 8 => backspace, 46 => delete
12972
        parent = target.parentNode;
12973
        // delete the <img>
12974
        parent.removeChild(target);
12975
        // and it's parent <a> too if it hasn't got any other child nodes
12976
        if (parent.nodeName === "A" && !parent.firstChild) {
12977
          parent.parentNode.removeChild(parent);
12978
        }
12979
12980
        setTimeout(function() { wysihtml5.quirks.redraw(element); }, 0);
12981
        event.preventDefault();
12982
      }
12983
    });
12984
12985
    // --------- IE 8+9 focus the editor when the iframe is clicked (without actually firing the 'focus' event on the <body>) ---------
12986
    if (!this.config.contentEditableMode && browser.hasIframeFocusIssue()) {
12987
      dom.observe(container, "focus", function() {
12988
        setTimeout(function() {
12989
          if (that.doc.querySelector(":focus") !== that.element) {
12990
            that.focus();
12991
          }
12992
        }, 0);
12993
      });
12994
12995
      dom.observe(this.element, "blur", function() {
12996
        setTimeout(function() {
12997
          that.selection.getSelection().removeAllRanges();
12998
        }, 0);
12999
      });
13000
    }
13001
13002
    // --------- Show url in tooltip when hovering links or images ---------
13003
    var titlePrefixes = {
13004
      IMG: "Image: ",
13005
      A:   "Link: "
13006
    };
13007
13008
    dom.observe(element, "mouseover", function(event) {
13009
      var target   = event.target,
13010
          nodeName = target.nodeName,
13011
          title;
13012
      if (nodeName !== "A" && nodeName !== "IMG") {
13013
        return;
13014
      }
13015
      var hasTitle = target.hasAttribute("title");
13016
      if(!hasTitle){
13017
        title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
13018
        target.setAttribute("title", title);
13019
      }
13020
    });
13021
  };
13022
})(wysihtml5);
13023
;/**
13024
 * Class that takes care that the value of the composer and the textarea is always in sync
13025
 */
13026
(function(wysihtml5) {
13027
  var INTERVAL = 400;
13028
13029
  wysihtml5.views.Synchronizer = Base.extend(
13030
    /** @scope wysihtml5.views.Synchronizer.prototype */ {
13031
13032
    constructor: function(editor, textarea, composer) {
13033
      this.editor   = editor;
13034
      this.textarea = textarea;
13035
      this.composer = composer;
13036
13037
      this._observe();
13038
    },
13039
13040
    /**
13041
     * Sync html from composer to textarea
13042
     * Takes care of placeholders
13043
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
13044
     */
13045
    fromComposerToTextarea: function(shouldParseHtml) {
13046
      this.textarea.setValue(wysihtml5.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
13047
    },
13048
13049
    /**
13050
     * Sync value of textarea to composer
13051
     * Takes care of placeholders
13052
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
13053
     */
13054
    fromTextareaToComposer: function(shouldParseHtml) {
13055
      var textareaValue = this.textarea.getValue(false, false);
13056
      if (textareaValue) {
13057
        this.composer.setValue(textareaValue, shouldParseHtml);
13058
      } else {
13059
        this.composer.clear();
13060
        this.editor.fire("set_placeholder");
13061
      }
13062
    },
13063
13064
    /**
13065
     * Invoke syncing based on view state
13066
     * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
13067
     */
13068
    sync: function(shouldParseHtml) {
13069
      if (this.editor.currentView.name === "textarea") {
13070
        this.fromTextareaToComposer(shouldParseHtml);
13071
      } else {
13072
        this.fromComposerToTextarea(shouldParseHtml);
13073
      }
13074
    },
13075
13076
    /**
13077
     * Initializes interval-based syncing
13078
     * also makes sure that on-submit the composer's content is synced with the textarea
13079
     * immediately when the form gets submitted
13080
     */
13081
    _observe: function() {
13082
      var interval,
13083
          that          = this,
13084
          form          = this.textarea.element.form,
13085
          startInterval = function() {
13086
            interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
13087
          },
13088
          stopInterval  = function() {
13089
            clearInterval(interval);
13090
            interval = null;
13091
          };
13092
13093
      startInterval();
13094
13095
      if (form) {
13096
        // If the textarea is in a form make sure that after onreset and onsubmit the composer
13097
        // has the correct state
13098
        wysihtml5.dom.observe(form, "submit", function() {
13099
          that.sync(true);
13100
        });
13101
        wysihtml5.dom.observe(form, "reset", function() {
13102
          setTimeout(function() { that.fromTextareaToComposer(); }, 0);
13103
        });
13104
      }
13105
13106
      this.editor.on("change_view", function(view) {
13107
        if (view === "composer" && !interval) {
13108
          that.fromTextareaToComposer(true);
13109
          startInterval();
13110
        } else if (view === "textarea") {
13111
          that.fromComposerToTextarea(true);
13112
          stopInterval();
13113
        }
13114
      });
13115
13116
      this.editor.on("destroy:composer", stopInterval);
13117
    }
13118
  });
13119
})(wysihtml5);
13120
;wysihtml5.views.Textarea = wysihtml5.views.View.extend(
13121
  /** @scope wysihtml5.views.Textarea.prototype */ {
13122
  name: "textarea",
13123
13124
  constructor: function(parent, textareaElement, config) {
13125
    this.base(parent, textareaElement, config);
13126
13127
    this._observe();
13128
  },
13129
13130
  clear: function() {
13131
    this.element.value = "";
13132
  },
13133
13134
  getValue: function(parse) {
13135
    var value = this.isEmpty() ? "" : this.element.value;
13136
    if (parse !== false) {
13137
      value = this.parent.parse(value);
13138
    }
13139
    return value;
13140
  },
13141
13142
  setValue: function(html, parse) {
13143
    if (parse) {
13144
      html = this.parent.parse(html);
13145
    }
13146
    this.element.value = html;
13147
  },
13148
13149
  cleanUp: function() {
13150
      var html = this.parent.parse(this.element.value);
13151
      this.element.value = html;
13152
  },
13153
13154
  hasPlaceholderSet: function() {
13155
    var supportsPlaceholder = wysihtml5.browser.supportsPlaceholderAttributeOn(this.element),
13156
        placeholderText     = this.element.getAttribute("placeholder") || null,
13157
        value               = this.element.value,
13158
        isEmpty             = !value;
13159
    return (supportsPlaceholder && isEmpty) || (value === placeholderText);
13160
  },
13161
13162
  isEmpty: function() {
13163
    return !wysihtml5.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
13164
  },
13165
13166
  _observe: function() {
13167
    var element = this.element,
13168
        parent  = this.parent,
13169
        eventMapping = {
13170
          focusin:  "focus",
13171
          focusout: "blur"
13172
        },
13173
        /**
13174
         * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
13175
         * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
13176
         */
13177
        events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
13178
13179
    parent.on("beforeload", function() {
13180
      wysihtml5.dom.observe(element, events, function(event) {
13181
        var eventName = eventMapping[event.type] || event.type;
13182
        parent.fire(eventName).fire(eventName + ":textarea");
13183
      });
13184
13185
      wysihtml5.dom.observe(element, ["paste", "drop"], function() {
13186
        setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
13187
      });
13188
    });
13189
  }
13190
});
13191
;/**
13192
 * WYSIHTML5 Editor
13193
 *
13194
 * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
0 ignored issues
show
Documentation introduced by
The parameter editableElement does not exist. Did you maybe forget to remove this comment?
Loading history...
13195
 * @param {Object} [config] See defaultConfig object below for explanation of each individual config option
0 ignored issues
show
Documentation introduced by
The parameter config does not exist. Did you maybe forget to remove this comment?
Loading history...
13196
 *
13197
 * @events
13198
 *    load
13199
 *    beforeload (for internal use only)
13200
 *    focus
13201
 *    focus:composer
13202
 *    focus:textarea
13203
 *    blur
13204
 *    blur:composer
13205
 *    blur:textarea
13206
 *    change
13207
 *    change:composer
13208
 *    change:textarea
13209
 *    paste
13210
 *    paste:composer
13211
 *    paste:textarea
13212
 *    newword:composer
13213
 *    destroy:composer
13214
 *    undo:composer
13215
 *    redo:composer
13216
 *    beforecommand:composer
13217
 *    aftercommand:composer
13218
 *    enable:composer
13219
 *    disable:composer
13220
 *    change_view
13221
 */
13222
(function(wysihtml5) {
13223
  var undef;
13224
13225
  var defaultConfig = {
13226
    // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
13227
    name:                 undef,
0 ignored issues
show
Bug introduced by
The variable undef seems to be never initialized.
Loading history...
13228
    // Whether the editor should look like the textarea (by adopting styles)
13229
    style:                true,
13230
    // Id of the toolbar element, pass falsey value if you don't want any toolbar logic
13231
    toolbar:              undef,
13232
    // Whether toolbar is displayed after init by script automatically.
13233
    // Can be set to false if toolobar is set to display only on editable area focus
13234
    showToolbarAfterInit: true,
13235
    // Whether urls, entered by the user should automatically become clickable-links
13236
    autoLink:             true,
13237
    // Includes table editing events and cell selection tracking
13238
    handleTables:         true,
13239
    // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
13240
    handleTabKey:         true,
13241
    // Object which includes parser rules to apply when html gets cleaned
13242
    // See parser_rules/*.js for examples
13243
    parserRules:          { tags: { br: {}, span: {}, div: {}, p: {} }, classes: {} },
13244
    // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
13245
    pasteParserRulesets: null,
13246
    // Parser method to use when the user inserts content
13247
    parser:               wysihtml5.dom.parse,
13248
    // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
13249
    composerClassName:    "wysihtml5-editor",
13250
    // Class name to add to the body when the wysihtml5 editor is supported
13251
    bodyClassName:        "wysihtml5-supported",
13252
    // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
13253
    useLineBreaks:        true,
13254
    // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
13255
    stylesheets:          [],
13256
    // Placeholder text to use, defaults to the placeholder attribute on the textarea element
13257
    placeholderText:      undef,
13258
    // Whether the rich text editor should be rendered on touch devices (wysihtml5 >= 0.3.0 comes with basic support for iOS 5)
13259
    supportTouchDevices:  true,
13260
    // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
13261
    cleanUp:              true,
13262
    // Whether to use div instead of secure iframe
13263
    contentEditableMode: false,
13264
    // Classname of container that editor should not touch and pass through
13265
    // Pass false to disable
13266
    uneditableContainerClassname: "wysihtml5-uneditable-container",
13267
    // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
13268
    // Also copied source is based directly on selection - 
13269
    // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
13270
    // If falsy value is passed source override is also disabled
13271
    copyedFromMarking: '<meta name="copied-from" content="wysihtml5">'
13272
  };
13273
13274
  wysihtml5.Editor = wysihtml5.lang.Dispatcher.extend(
13275
    /** @scope wysihtml5.Editor.prototype */ {
13276
    constructor: function(editableElement, config) {
13277
      this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
13278
      this.config           = wysihtml5.lang.object({}).merge(defaultConfig).merge(config).get();
13279
      this._isCompatible    = wysihtml5.browser.supported();
13280
13281
      if (this.editableElement.nodeName.toLowerCase() != "textarea") {
13282
          this.config.contentEditableMode = true;
13283
          this.config.noTextarea = true;
13284
      }
13285
      if (!this.config.noTextarea) {
13286
          this.textarea         = new wysihtml5.views.Textarea(this, this.editableElement, this.config);
13287
          this.currentView      = this.textarea;
13288
      }
13289
13290
      // Sort out unsupported/unwanted browsers here
13291
      if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml5.browser.isTouchDevice())) {
13292
        var that = this;
13293
        setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
13294
        return;
13295
      }
13296
13297
      // Add class name to body, to indicate that the editor is supported
13298
      wysihtml5.dom.addClass(document.body, this.config.bodyClassName);
13299
13300
      this.composer = new wysihtml5.views.Composer(this, this.editableElement, this.config);
13301
      this.currentView = this.composer;
13302
13303
      if (typeof(this.config.parser) === "function") {
13304
        this._initParser();
13305
      }
13306
13307
      this.on("beforeload", this.handleBeforeLoad);
13308
    },
13309
13310
    handleBeforeLoad: function() {
13311
        if (!this.config.noTextarea) {
13312
            this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
13313
        }
13314
        if (this.config.toolbar) {
13315
          this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar, this.config.showToolbarAfterInit);
13316
        }
13317
    },
13318
13319
    isCompatible: function() {
13320
      return this._isCompatible;
13321
    },
13322
13323
    clear: function() {
13324
      this.currentView.clear();
13325
      return this;
13326
    },
13327
13328
    getValue: function(parse, clearInternals) {
13329
      return this.currentView.getValue(parse, clearInternals);
13330
    },
13331
13332
    setValue: function(html, parse) {
13333
      this.fire("unset_placeholder");
13334
13335
      if (!html) {
13336
        return this.clear();
13337
      }
13338
13339
      this.currentView.setValue(html, parse);
13340
      return this;
13341
    },
13342
13343
    cleanUp: function() {
13344
        this.currentView.cleanUp();
13345
    },
13346
13347
    focus: function(setToEnd) {
13348
      this.currentView.focus(setToEnd);
13349
      return this;
13350
    },
13351
13352
    /**
13353
     * Deactivate editor (make it readonly)
13354
     */
13355
    disable: function() {
13356
      this.currentView.disable();
13357
      return this;
13358
    },
13359
13360
    /**
13361
     * Activate editor
13362
     */
13363
    enable: function() {
13364
      this.currentView.enable();
13365
      return this;
13366
    },
13367
13368
    isEmpty: function() {
13369
      return this.currentView.isEmpty();
13370
    },
13371
13372
    hasPlaceholderSet: function() {
13373
      return this.currentView.hasPlaceholderSet();
13374
    },
13375
13376
    parse: function(htmlOrElement, clearInternals) {
13377
      var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
13378
      var returnValue = this.config.parser(htmlOrElement, {
13379
        "rules": this.config.parserRules,
13380
        "cleanUp": this.config.cleanUp,
13381
        "context": parseContext,
13382
        "uneditableClass": this.config.uneditableContainerClassname,
13383
        "clearInternals" : clearInternals
13384
      });
13385
      if (typeof(htmlOrElement) === "object") {
13386
        wysihtml5.quirks.redraw(htmlOrElement);
13387
      }
13388
      return returnValue;
13389
    },
13390
13391
    /**
13392
     * Prepare html parser logic
13393
     *  - Observes for paste and drop
13394
     */
13395
    _initParser: function() {
13396
      var that = this,
13397
          oldHtml,
13398
          cleanHtml;
0 ignored issues
show
Unused Code introduced by
The variable cleanHtml seems to be never used. Consider removing it.
Loading history...
13399
13400
      if (wysihtml5.browser.supportsModenPaste()) {
13401
        this.on("paste:composer", function(event) {
13402
          event.preventDefault();
13403
          oldHtml = wysihtml5.dom.getPastedHtml(event);
13404
          if (oldHtml) {
13405
            that._cleanAndPaste(oldHtml);
13406
          }
13407
        });
13408
13409
      } else {
13410
        this.on("beforepaste:composer", function(event) {
13411
          event.preventDefault();
13412
          wysihtml5.dom.getPastedHtmlWithDiv(that.composer, function(pastedHTML) {
13413
            if (pastedHTML) {
13414
              that._cleanAndPaste(pastedHTML);
13415
            }
13416
          });
13417
        });
13418
13419
      }
13420
    },
13421
13422
    _cleanAndPaste: function (oldHtml) {
13423
      var cleanHtml = wysihtml5.quirks.cleanPastedHTML(oldHtml, {
13424
        "referenceNode": this.composer.element,
13425
        "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
13426
        "uneditableClass": this.config.uneditableContainerClassname
13427
      });
13428
      this.composer.selection.deleteContents();
13429
      this.composer.selection.insertHTML(cleanHtml);
13430
    }
13431
  });
13432
})(wysihtml5);
13433
;/**
13434
 * Toolbar Dialog
13435
 *
13436
 * @param {Element} link The toolbar link which causes the dialog to show up
0 ignored issues
show
Documentation introduced by
The parameter link does not exist. Did you maybe forget to remove this comment?
Loading history...
13437
 * @param {Element} container The dialog container
0 ignored issues
show
Documentation introduced by
The parameter container does not exist. Did you maybe forget to remove this comment?
Loading history...
13438
 *
13439
 * @example
13440
 *    <!-- Toolbar link -->
13441
 *    <a data-wysihtml5-command="insertImage">insert an image</a>
13442
 *
13443
 *    <!-- Dialog -->
13444
 *    <div data-wysihtml5-dialog="insertImage" style="display: none;">
13445
 *      <label>
13446
 *        URL: <input data-wysihtml5-dialog-field="src" value="http://">
13447
 *      </label>
13448
 *      <label>
13449
 *        Alternative text: <input data-wysihtml5-dialog-field="alt" value="">
13450
 *      </label>
13451
 *    </div>
13452
 *
13453
 *    <script>
13454
 *      var dialog = new wysihtml5.toolbar.Dialog(
13455
 *        document.querySelector("[data-wysihtml5-command='insertImage']"),
13456
 *        document.querySelector("[data-wysihtml5-dialog='insertImage']")
13457
 *      );
13458
 *      dialog.observe("save", function(attributes) {
13459
 *        // do something
13460
 *      });
13461
 *    </script>
13462
 */
13463
(function(wysihtml5) {
13464
  var dom                     = wysihtml5.dom,
13465
      CLASS_NAME_OPENED       = "wysihtml5-command-dialog-opened",
13466
      SELECTOR_FORM_ELEMENTS  = "input, select, textarea",
13467
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
13468
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
13469
13470
13471
  wysihtml5.toolbar.Dialog = wysihtml5.lang.Dispatcher.extend(
13472
    /** @scope wysihtml5.toolbar.Dialog.prototype */ {
13473
    constructor: function(link, container) {
13474
      this.link       = link;
13475
      this.container  = container;
13476
    },
13477
13478
    _observe: function() {
13479
      if (this._observed) {
13480
        return;
13481
      }
13482
13483
      var that = this,
13484
          callbackWrapper = function(event) {
13485
            var attributes = that._serialize();
13486
            if (attributes == that.elementToChange) {
13487
              that.fire("edit", attributes);
13488
            } else {
13489
              that.fire("save", attributes);
13490
            }
13491
            that.hide();
13492
            event.preventDefault();
13493
            event.stopPropagation();
13494
          };
13495
13496
      dom.observe(that.link, "click", function() {
13497
        if (dom.hasClass(that.link, CLASS_NAME_OPENED)) {
13498
          setTimeout(function() { that.hide(); }, 0);
13499
        }
13500
      });
13501
13502
      dom.observe(this.container, "keydown", function(event) {
13503
        var keyCode = event.keyCode;
13504
        if (keyCode === wysihtml5.ENTER_KEY) {
13505
          callbackWrapper(event);
13506
        }
13507
        if (keyCode === wysihtml5.ESCAPE_KEY) {
13508
          that.fire("cancel");
13509
          that.hide();
13510
        }
13511
      });
13512
13513
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=save]", "click", callbackWrapper);
13514
13515
      dom.delegate(this.container, "[data-wysihtml5-dialog-action=cancel]", "click", function(event) {
13516
        that.fire("cancel");
13517
        that.hide();
13518
        event.preventDefault();
13519
        event.stopPropagation();
13520
      });
13521
13522
      var formElements  = this.container.querySelectorAll(SELECTOR_FORM_ELEMENTS),
13523
          i             = 0,
13524
          length        = formElements.length,
13525
          _clearInterval = function() { clearInterval(that.interval); };
13526
      for (; i<length; i++) {
13527
        dom.observe(formElements[i], "change", _clearInterval);
13528
      }
13529
13530
      this._observed = true;
13531
    },
13532
13533
    /**
13534
     * Grabs all fields in the dialog and puts them in key=>value style in an object which
13535
     * then gets returned
13536
     */
13537
    _serialize: function() {
13538
      var data    = this.elementToChange || {},
13539
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
13540
          length  = fields.length,
13541
          i       = 0;
13542
13543
      for (; i<length; i++) {
13544
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
13545
      }
13546
      return data;
13547
    },
13548
13549
    /**
13550
     * Takes the attributes of the "elementToChange"
13551
     * and inserts them in their corresponding dialog input fields
13552
     *
13553
     * Assume the "elementToChange" looks like this:
13554
     *    <a href="http://www.google.com" target="_blank">foo</a>
13555
     *
13556
     * and we have the following dialog:
13557
     *    <input type="text" data-wysihtml5-dialog-field="href" value="">
13558
     *    <input type="text" data-wysihtml5-dialog-field="target" value="">
13559
     *
13560
     * after calling _interpolate() the dialog will look like this
13561
     *    <input type="text" data-wysihtml5-dialog-field="href" value="http://www.google.com">
13562
     *    <input type="text" data-wysihtml5-dialog-field="target" value="_blank">
13563
     *
13564
     * Basically it adopted the attribute values into the corresponding input fields
13565
     *
13566
     */
13567
    _interpolate: function(avoidHiddenFields) {
13568
      var field,
13569
          fieldName,
13570
          newValue,
13571
          focusedElement = document.querySelector(":focus"),
13572
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
13573
          length         = fields.length,
13574
          i              = 0;
13575
      for (; i<length; i++) {
13576
        field = fields[i];
13577
13578
        // Never change elements where the user is currently typing in
13579
        if (field === focusedElement) {
13580
          continue;
13581
        }
13582
13583
        // Don't update hidden fields
13584
        // See https://github.com/xing/wysihtml5/pull/14
13585
        if (avoidHiddenFields && field.type === "hidden") {
13586
          continue;
13587
        }
13588
13589
        fieldName = field.getAttribute(ATTRIBUTE_FIELDS);
13590
        newValue  = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue;
13591
        field.value = newValue;
13592
      }
13593
    },
13594
13595
    /**
13596
     * Show the dialog element
13597
     */
13598
    show: function(elementToChange) {
13599
      if (dom.hasClass(this.link, CLASS_NAME_OPENED)) {
13600
        return;
13601
      }
13602
13603
      var that        = this,
13604
          firstField  = this.container.querySelector(SELECTOR_FORM_ELEMENTS);
13605
      this.elementToChange = elementToChange;
13606
      this._observe();
13607
      this._interpolate();
13608
      if (elementToChange) {
13609
        this.interval = setInterval(function() { that._interpolate(true); }, 500);
13610
      }
13611
      dom.addClass(this.link, CLASS_NAME_OPENED);
13612
      this.container.style.display = "";
13613
      this.fire("show");
13614
      if (firstField && !elementToChange) {
13615
        try {
13616
          firstField.focus();
13617
        } 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...
13618
      }
13619
    },
13620
13621
    /**
13622
     * Hide the dialog element
13623
     */
13624
    hide: function() {
13625
      clearInterval(this.interval);
13626
      this.elementToChange = null;
13627
      dom.removeClass(this.link, CLASS_NAME_OPENED);
13628
      this.container.style.display = "none";
13629
      this.fire("hide");
13630
    }
13631
  });
13632
})(wysihtml5);
13633
;/**
13634
 * Converts speech-to-text and inserts this into the editor
13635
 * As of now (2011/03/25) this only is supported in Chrome >= 11
13636
 *
13637
 * Note that it sends the recorded audio to the google speech recognition api:
13638
 * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec
13639
 *
13640
 * Current HTML5 draft can be found here
13641
 * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html
13642
 *
13643
 * "Accessing Google Speech API Chrome 11"
13644
 * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
13645
 */
13646
(function(wysihtml5) {
13647
  var dom = wysihtml5.dom;
13648
13649
  var linkStyles = {
13650
    position: "relative"
13651
  };
13652
13653
  var wrapperStyles = {
13654
    left:     0,
13655
    margin:   0,
13656
    opacity:  0,
13657
    overflow: "hidden",
13658
    padding:  0,
13659
    position: "absolute",
13660
    top:      0,
13661
    zIndex:   1
13662
  };
13663
13664
  var inputStyles = {
13665
    cursor:     "inherit",
13666
    fontSize:   "50px",
13667
    height:     "50px",
13668
    marginTop:  "-25px",
13669
    outline:    0,
13670
    padding:    0,
13671
    position:   "absolute",
13672
    right:      "-4px",
13673
    top:        "50%"
13674
  };
13675
13676
  var inputAttributes = {
13677
    "x-webkit-speech": "",
13678
    "speech":          ""
13679
  };
13680
13681
  wysihtml5.toolbar.Speech = function(parent, link) {
13682
    var input = document.createElement("input");
13683
    if (!wysihtml5.browser.supportsSpeechApiOn(input)) {
13684
      link.style.display = "none";
13685
      return;
13686
    }
13687
    var lang = parent.editor.textarea.element.getAttribute("lang");
13688
    if (lang) {
13689
      inputAttributes.lang = lang;
13690
    }
13691
13692
    var wrapper = document.createElement("div");
13693
13694
    wysihtml5.lang.object(wrapperStyles).merge({
13695
      width:  link.offsetWidth  + "px",
13696
      height: link.offsetHeight + "px"
13697
    });
13698
13699
    dom.insert(input).into(wrapper);
13700
    dom.insert(wrapper).into(link);
13701
13702
    dom.setStyles(inputStyles).on(input);
13703
    dom.setAttributes(inputAttributes).on(input);
13704
13705
    dom.setStyles(wrapperStyles).on(wrapper);
13706
    dom.setStyles(linkStyles).on(link);
13707
13708
    var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange";
13709
    dom.observe(input, eventName, function() {
13710
      parent.execCommand("insertText", input.value);
13711
      input.value = "";
13712
    });
13713
13714
    dom.observe(input, "click", function(event) {
13715
      if (dom.hasClass(link, "wysihtml5-command-disabled")) {
13716
        event.preventDefault();
13717
      }
13718
13719
      event.stopPropagation();
13720
    });
13721
  };
13722
})(wysihtml5);
13723
;/**
13724
 * Toolbar
13725
 *
13726
 * @param {Object} parent Reference to instance of Editor instance
0 ignored issues
show
Documentation introduced by
The parameter parent does not exist. Did you maybe forget to remove this comment?
Loading history...
13727
 * @param {Element} container Reference to the toolbar container element
0 ignored issues
show
Documentation introduced by
The parameter container does not exist. Did you maybe forget to remove this comment?
Loading history...
13728
 *
13729
 * @example
13730
 *    <div id="toolbar">
13731
 *      <a data-wysihtml5-command="createLink">insert link</a>
13732
 *      <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">insert h1</a>
13733
 *    </div>
13734
 *
13735
 *    <script>
13736
 *      var toolbar = new wysihtml5.toolbar.Toolbar(editor, document.getElementById("toolbar"));
13737
 *    </script>
13738
 */
13739
(function(wysihtml5) {
13740
  var CLASS_NAME_COMMAND_DISABLED   = "wysihtml5-command-disabled",
13741
      CLASS_NAME_COMMANDS_DISABLED  = "wysihtml5-commands-disabled",
13742
      CLASS_NAME_COMMAND_ACTIVE     = "wysihtml5-command-active",
13743
      CLASS_NAME_ACTION_ACTIVE      = "wysihtml5-action-active",
13744
      dom                           = wysihtml5.dom;
13745
13746
  wysihtml5.toolbar.Toolbar = Base.extend(
13747
    /** @scope wysihtml5.toolbar.Toolbar.prototype */ {
13748
    constructor: function(editor, container, showOnInit) {
13749
      this.editor     = editor;
13750
      this.container  = typeof(container) === "string" ? document.getElementById(container) : container;
13751
      this.composer   = editor.composer;
13752
13753
      this._getLinks("command");
13754
      this._getLinks("action");
13755
13756
      this._observe();
13757
      if (showOnInit) { this.show(); }
13758
13759
      if (editor.config.classNameCommandDisabled != null) {
13760
        CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled;
13761
      }
13762
      if (editor.config.classNameCommandsDisabled != null) {
13763
        CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled;
13764
      }
13765
      if (editor.config.classNameCommandActive != null) {
13766
        CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive;
13767
      }
13768
      if (editor.config.classNameActionActive != null) {
13769
        CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive;
13770
      }
13771
13772
      var speechInputLinks  = this.container.querySelectorAll("[data-wysihtml5-command=insertSpeech]"),
13773
          length            = speechInputLinks.length,
13774
          i                 = 0;
13775
      for (; i<length; i++) {
13776
        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...
13777
      }
13778
    },
13779
13780
    _getLinks: function(type) {
13781
      var links   = this[type + "Links"] = wysihtml5.lang.array(this.container.querySelectorAll("[data-wysihtml5-" + type + "]")).get(),
13782
          length  = links.length,
13783
          i       = 0,
13784
          mapping = this[type + "Mapping"] = {},
13785
          link,
13786
          group,
13787
          name,
13788
          value,
13789
          dialog;
13790
      for (; i<length; i++) {
13791
        link    = links[i];
13792
        name    = link.getAttribute("data-wysihtml5-" + type);
13793
        value   = link.getAttribute("data-wysihtml5-" + type + "-value");
13794
        group   = this.container.querySelector("[data-wysihtml5-" + type + "-group='" + name + "']");
13795
        dialog  = this._getDialog(link, name);
13796
13797
        mapping[name + ":" + value] = {
13798
          link:   link,
13799
          group:  group,
13800
          name:   name,
13801
          value:  value,
13802
          dialog: dialog,
13803
          state:  false
13804
        };
13805
      }
13806
    },
13807
13808
    _getDialog: function(link, command) {
13809
      var that          = this,
13810
          dialogElement = this.container.querySelector("[data-wysihtml5-dialog='" + command + "']"),
13811
          dialog,
13812
          caretBookmark;
13813
13814
      if (dialogElement) {
13815
        if (wysihtml5.toolbar["Dialog_" + command]) {
13816
            dialog = new wysihtml5.toolbar["Dialog_" + command](link, dialogElement);
13817
        } else {
13818
            dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
13819
        }
13820
13821
        dialog.on("show", function() {
13822
          caretBookmark = that.composer.selection.getBookmark();
13823
13824
          that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13825
        });
13826
13827
        dialog.on("save", function(attributes) {
13828
          if (caretBookmark) {
13829
            that.composer.selection.setBookmark(caretBookmark);
13830
          }
13831
          that._execCommand(command, attributes);
13832
13833
          that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13834
        });
13835
13836
        dialog.on("cancel", function() {
13837
          that.editor.focus(false);
13838
          that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
13839
        });
13840
      }
13841
      return dialog;
0 ignored issues
show
Bug introduced by
The variable dialog does not seem to be initialized in case dialogElement on line 13814 is false. Are you sure this can never be the case?
Loading history...
13842
    },
13843
13844
    /**
13845
     * @example
13846
     *    var toolbar = new wysihtml5.Toolbar();
13847
     *    // Insert a <blockquote> element or wrap current selection in <blockquote>
13848
     *    toolbar.execCommand("formatBlock", "blockquote");
13849
     */
13850
    execCommand: function(command, commandValue) {
13851
      if (this.commandsDisabled) {
13852
        return;
13853
      }
13854
13855
      var commandObj = this.commandMapping[command + ":" + commandValue];
13856
13857
      // Show dialog when available
13858
      if (commandObj && commandObj.dialog && !commandObj.state) {
13859
        commandObj.dialog.show();
13860
      } else {
13861
        this._execCommand(command, commandValue);
13862
      }
13863
    },
13864
13865
    _execCommand: function(command, commandValue) {
13866
      // Make sure that composer is focussed (false => don't move caret to the end)
13867
      this.editor.focus(false);
13868
13869
      this.composer.commands.exec(command, commandValue);
13870
      this._updateLinkStates();
13871
    },
13872
13873
    execAction: function(action) {
13874
      var editor = this.editor;
13875
      if (action === "change_view") {
13876
        if (editor.textarea) {
13877
            if (editor.currentView === editor.textarea) {
13878
              editor.fire("change_view", "composer");
13879
            } else {
13880
              editor.fire("change_view", "textarea");
13881
            }
13882
        }
13883
      }
13884
      if (action == "showSource") {
13885
          editor.fire("showSource");
13886
      }
13887
    },
13888
13889
    _observe: function() {
13890
      var that      = this,
13891
          editor    = this.editor,
13892
          container = this.container,
13893
          links     = this.commandLinks.concat(this.actionLinks),
13894
          length    = links.length,
13895
          i         = 0;
13896
13897
      for (; i<length; i++) {
13898
        // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied
13899
        // (you know, a:link { ... } doesn't match anchors with missing href attribute)
13900
        if (links[i].nodeName === "A") {
13901
          dom.setAttributes({
13902
            href:         "javascript:;",
13903
            unselectable: "on"
13904
          }).on(links[i]);
13905
        } else {
13906
          dom.setAttributes({ unselectable: "on" }).on(links[i]);
13907
        }
13908
      }
13909
13910
      // Needed for opera and chrome
13911
      dom.delegate(container, "[data-wysihtml5-command], [data-wysihtml5-action]", "mousedown", function(event) { event.preventDefault(); });
13912
13913
      dom.delegate(container, "[data-wysihtml5-command]", "click", function(event) {
13914
        var link          = this,
13915
            command       = link.getAttribute("data-wysihtml5-command"),
13916
            commandValue  = link.getAttribute("data-wysihtml5-command-value");
13917
        that.execCommand(command, commandValue);
13918
        event.preventDefault();
13919
      });
13920
13921
      dom.delegate(container, "[data-wysihtml5-action]", "click", function(event) {
13922
        var action = this.getAttribute("data-wysihtml5-action");
13923
        that.execAction(action);
13924
        event.preventDefault();
13925
      });
13926
13927
      editor.on("interaction:composer", function() {
13928
          that._updateLinkStates();
13929
      });
13930
13931
      editor.on("focus:composer", function() {
13932
        that.bookmark = null;
13933
      });
13934
13935
      if (this.editor.config.handleTables) {
13936
          editor.on("tableselect:composer", function() {
13937
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "";
13938
          });
13939
          editor.on("tableunselect:composer", function() {
13940
              that.container.querySelectorAll('[data-wysihtml5-hiddentools="table"]')[0].style.display = "none";
13941
          });
13942
      }
13943
13944
      editor.on("change_view", function(currentView) {
13945
        // Set timeout needed in order to let the blur event fire first
13946
        if (editor.textarea) {
13947
            setTimeout(function() {
13948
              that.commandsDisabled = (currentView !== "composer");
13949
              that._updateLinkStates();
13950
              if (that.commandsDisabled) {
13951
                dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED);
13952
              } else {
13953
                dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED);
13954
              }
13955
            }, 0);
13956
        }
13957
      });
13958
    },
13959
13960
    _updateLinkStates: function() {
13961
13962
      var commandMapping    = this.commandMapping,
13963
          actionMapping     = this.actionMapping,
13964
          i,
13965
          state,
13966
          action,
13967
          command;
13968
      // every millisecond counts... this is executed quite often
13969
      for (i in commandMapping) {
13970
        command = commandMapping[i];
13971
        if (this.commandsDisabled) {
13972
          state = false;
13973
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13974
          if (command.group) {
13975
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13976
          }
13977
          if (command.dialog) {
13978
            command.dialog.hide();
13979
          }
13980
        } else {
13981
          state = this.composer.commands.state(command.name, command.value);
13982
          dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED);
13983
          if (command.group) {
13984
            dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED);
13985
          }
13986
        }
13987
        if (command.state === state) {
13988
          continue;
13989
        }
13990
13991
        command.state = state;
13992
        if (state) {
13993
          dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
13994
          if (command.group) {
13995
            dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
13996
          }
13997
          if (command.dialog) {
13998
            if (typeof(state) === "object" || wysihtml5.lang.object(state).isArray()) {
13999
14000
              if (!command.dialog.multiselect && wysihtml5.lang.object(state).isArray()) {
14001
                // Grab first and only object/element in state array, otherwise convert state into boolean
14002
                // to avoid showing a dialog for multiple selected elements which may have different attributes
14003
                // eg. when two links with different href are selected, the state will be an array consisting of both link elements
14004
                // but the dialog interface can only update one
14005
                state = state.length === 1 ? state[0] : true;
14006
                command.state = state;
14007
              }
14008
              command.dialog.show(state);
14009
            } else {
14010
              command.dialog.hide();
14011
            }
14012
          }
14013
        } else {
14014
          dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE);
14015
          if (command.group) {
14016
            dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE);
14017
          }
14018
          if (command.dialog) {
14019
            command.dialog.hide();
14020
          }
14021
        }
14022
      }
14023
14024
      for (i in actionMapping) {
14025
        action = actionMapping[i];
14026
14027
        if (action.name === "change_view") {
14028
          action.state = this.editor.currentView === this.editor.textarea;
14029
          if (action.state) {
14030
            dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE);
14031
          } else {
14032
            dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE);
14033
          }
14034
        }
14035
      }
14036
    },
14037
14038
    show: function() {
14039
      this.container.style.display = "";
14040
    },
14041
14042
    hide: function() {
14043
      this.container.style.display = "none";
14044
    }
14045
  });
14046
14047
})(wysihtml5);
14048
;(function(wysihtml5) {
14049
    wysihtml5.toolbar.Dialog_createTable = wysihtml5.toolbar.Dialog.extend({
14050
        show: function(elementToChange) {
14051
            this.base(elementToChange);
14052
14053
        }
14054
14055
    });
14056
})(wysihtml5);
14057
;(function(wysihtml5) {
14058
  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...
14059
      SELECTOR_FIELDS         = "[data-wysihtml5-dialog-field]",
14060
      ATTRIBUTE_FIELDS        = "data-wysihtml5-dialog-field";
14061
14062
  wysihtml5.toolbar.Dialog_foreColorStyle = wysihtml5.toolbar.Dialog.extend({
14063
    multiselect: true,
14064
14065
    _serialize: function() {
14066
      var data    = {},
14067
          fields  = this.container.querySelectorAll(SELECTOR_FIELDS),
14068
          length  = fields.length,
14069
          i       = 0;
14070
14071
      for (; i<length; i++) {
14072
        data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value;
14073
      }
14074
      return data;
14075
    },
14076
14077
    _interpolate: function(avoidHiddenFields) {
14078
      var field,
14079
          fieldName,
0 ignored issues
show
Unused Code introduced by
The variable fieldName seems to be never used. Consider removing it.
Loading history...
14080
          newValue,
0 ignored issues
show
Unused Code introduced by
The variable newValue seems to be never used. Consider removing it.
Loading history...
14081
          focusedElement = document.querySelector(":focus"),
14082
          fields         = this.container.querySelectorAll(SELECTOR_FIELDS),
14083
          length         = fields.length,
14084
          i              = 0,
14085
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
14086
          colorStr       = (firstElement) ? firstElement.getAttribute('style') : null,
14087
          color          = (colorStr) ? wysihtml5.quirks.styleParser.parseColor(colorStr, "color") : null;
14088
14089
      for (; i<length; i++) {
14090
        field = fields[i];
14091
        // Never change elements where the user is currently typing in
14092
        if (field === focusedElement) {
14093
          continue;
14094
        }
14095
        // Don't update hidden fields3
14096
        if (avoidHiddenFields && field.type === "hidden") {
14097
          continue;
14098
        }
14099
        if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") {
14100
          if (color) {
14101
            if (color[3] && color[3] != 1) {
14102
              field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");";
14103
            } else {
14104
              field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");";
14105
            }
14106
          } else {
14107
            field.value = "rgb(0,0,0);";
14108
          }
14109
        }
14110
      }
14111
    }
14112
14113
  });
14114
})(wysihtml5);
14115
;(function(wysihtml5) {
14116
  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...
14117
      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...
14118
      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...
14119
14120
  wysihtml5.toolbar.Dialog_fontSizeStyle = wysihtml5.toolbar.Dialog.extend({
14121
    multiselect: true,
14122
14123
    _serialize: function() {
14124
      return {"size" : this.container.querySelector('[data-wysihtml5-dialog-field="size"]').value};
14125
    },
14126
14127
    _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...
14128
      var focusedElement = document.querySelector(":focus"),
14129
          field          = this.container.querySelector("[data-wysihtml5-dialog-field='size']"),
14130
          firstElement   = (this.elementToChange) ? ((wysihtml5.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null,
14131
          styleStr       = (firstElement) ? firstElement.getAttribute('style') : null,
14132
          size           = (styleStr) ? wysihtml5.quirks.styleParser.parseFontSize(styleStr) : null;
14133
14134
      if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) {
14135
        field.value = size;
14136
      }
14137
    }
14138
14139
  });
14140
})(wysihtml5);
14141
/*!
14142
14143
 handlebars v1.3.0
14144
14145
Copyright (C) 2011 by Yehuda Katz
14146
14147
Permission is hereby granted, free of charge, to any person obtaining a copy
14148
of this software and associated documentation files (the "Software"), to deal
14149
in the Software without restriction, including without limitation the rights
14150
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14151
copies of the Software, and to permit persons to whom the Software is
14152
furnished to do so, subject to the following conditions:
14153
14154
The above copyright notice and this permission notice shall be included in
14155
all copies or substantial portions of the Software.
14156
14157
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14158
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14159
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14160
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14161
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
14162
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
14163
THE SOFTWARE.
14164
14165
@license
14166
*/
14167
var Handlebars=function(){var a=function(){"use strict";function a(a){this.string=a}var b;return a.prototype.toString=function(){return""+this.string},b=a}(),b=function(a){"use strict";function b(a){return h[a]||"&amp;"}function c(a,b){for(var c in b)Object.prototype.hasOwnProperty.call(b,c)&&(a[c]=b[c])}function d(a){return a instanceof g?a.toString():a||0===a?(a=""+a,j.test(a)?a.replace(i,b):a):""}function e(a){return a||0===a?m(a)&&0===a.length?!0:!1:!0}var f={},g=a,h={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},i=/[&<>"'`]/g,j=/[&<>"'`]/;f.extend=c;var k=Object.prototype.toString;f.toString=k;var l=function(a){return"function"==typeof a};l(/x/)&&(l=function(a){return"function"==typeof a&&"[object Function]"===k.call(a)});var l;f.isFunction=l;var m=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===k.call(a):!1};return f.isArray=m,f.escapeExpression=d,f.isEmpty=e,f}(a),c=function(){"use strict";function a(a,b){var d;b&&b.firstLine&&(d=b.firstLine,a+=" - "+d+":"+b.firstColumn);for(var e=Error.prototype.constructor.call(this,a),f=0;f<c.length;f++)this[c[f]]=e[c[f]];d&&(this.lineNumber=d,this.column=b.firstColumn)}var b,c=["description","fileName","lineNumber","message","name","number","stack"];return a.prototype=new Error,b=a}(),d=function(a,b){"use strict";function c(a,b){this.helpers=a||{},this.partials=b||{},d(this)}function d(a){a.registerHelper("helperMissing",function(a){if(2===arguments.length)return void 0;throw new h("Missing helper: '"+a+"'")}),a.registerHelper("blockHelperMissing",function(b,c){var d=c.inverse||function(){},e=c.fn;return m(b)&&(b=b.call(this)),b===!0?e(this):b===!1||null==b?d(this):l(b)?b.length>0?a.helpers.each(b,c):d(this):e(b)}),a.registerHelper("each",function(a,b){var c,d=b.fn,e=b.inverse,f=0,g="";if(m(a)&&(a=a.call(this)),b.data&&(c=q(b.data)),a&&"object"==typeof a)if(l(a))for(var h=a.length;h>f;f++)c&&(c.index=f,c.first=0===f,c.last=f===a.length-1),g+=d(a[f],{data:c});else for(var i in a)a.hasOwnProperty(i)&&(c&&(c.key=i,c.index=f,c.first=0===f),g+=d(a[i],{data:c}),f++);return 0===f&&(g=e(this)),g}),a.registerHelper("if",function(a,b){return m(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||g.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})}),a.registerHelper("with",function(a,b){return m(a)&&(a=a.call(this)),g.isEmpty(a)?void 0:b.fn(a)}),a.registerHelper("log",function(b,c){var d=c.data&&null!=c.data.level?parseInt(c.data.level,10):1;a.log(d,b)})}function e(a,b){p.log(a,b)}var f={},g=a,h=b,i="1.3.0";f.VERSION=i;var j=4;f.COMPILER_REVISION=j;var k={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:">= 1.0.0"};f.REVISION_CHANGES=k;var l=g.isArray,m=g.isFunction,n=g.toString,o="[object Object]";f.HandlebarsEnvironment=c,c.prototype={constructor:c,logger:p,log:e,registerHelper:function(a,b,c){if(n.call(a)===o){if(c||b)throw new h("Arg not supported with multiple helpers");g.extend(this.helpers,a)}else c&&(b.not=c),this.helpers[a]=b},registerPartial:function(a,b){n.call(a)===o?g.extend(this.partials,a):this.partials[a]=b}};var p={methodMap:{0:"debug",1:"info",2:"warn",3:"error"},DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,log:function(a,b){if(p.level<=a){var c=p.methodMap[a];"undefined"!=typeof console&&console[c]&&console[c].call(console,b)}}};f.logger=p,f.log=e;var q=function(a){var b={};return g.extend(b,a),b};return f.createFrame=q,f}(b,c),e=function(a,b,c){"use strict";function d(a){var b=a&&a[0]||1,c=m;if(b!==c){if(c>b){var d=n[c],e=n[b];throw new l("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+d+") or downgrade your runtime to an older version ("+e+").")}throw new l("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function e(a,b){if(!b)throw new l("No environment passed to template");var c=function(a,c,d,e,f,g){var h=b.VM.invokePartial.apply(this,arguments);if(null!=h)return h;if(b.compile){var i={helpers:e,partials:f,data:g};return f[c]=b.compile(a,{data:void 0!==g},b),f[c](d,i)}throw new l("The partial "+c+" could not be compiled when running in runtime-only mode")},d={escapeExpression:k.escapeExpression,invokePartial:c,programs:[],program:function(a,b,c){var d=this.programs[a];return c?d=g(a,b,c):d||(d=this.programs[a]=g(a,b)),d},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c={},k.extend(c,b),k.extend(c,a)),c},programWithDepth:b.VM.programWithDepth,noop:b.VM.noop,compilerInfo:null};return function(c,e){e=e||{};var f,g,h=e.partial?e:b;e.partial||(f=e.helpers,g=e.partials);var i=a.call(d,h,c,f,g,e.data);return e.partial||b.VM.checkRevision(d.compilerInfo),i}}function f(a,b,c){var d=Array.prototype.slice.call(arguments,3),e=function(a,e){return e=e||{},b.apply(this,[a,e.data||c].concat(d))};return e.program=a,e.depth=d.length,e}function g(a,b,c){var d=function(a,d){return d=d||{},b(a,d.data||c)};return d.program=a,d.depth=0,d}function h(a,b,c,d,e,f){var g={partial:!0,helpers:d,partials:e,data:f};if(void 0===a)throw new l("The partial "+b+" could not be found");return a instanceof Function?a(c,g):void 0}function i(){return""}var j={},k=a,l=b,m=c.COMPILER_REVISION,n=c.REVISION_CHANGES;return j.checkRevision=d,j.template=e,j.programWithDepth=f,j.program=g,j.invokePartial=h,j.noop=i,j}(b,c,d),f=function(a,b,c,d,e){"use strict";var f,g=a,h=b,i=c,j=d,k=e,l=function(){var a=new g.HandlebarsEnvironment;return j.extend(a,g),a.SafeString=h,a.Exception=i,a.Utils=j,a.VM=k,a.template=function(b){return k.template(b,a)},a},m=l();return m.create=l,f=m}(d,a,c,b,e);return f}();this["wysihtml5"] = this["wysihtml5"] || {};
0 ignored issues
show
Bug introduced by
The variable g seems to not be initialized for all possible execution paths. Are you sure call handles undefined variables?
Loading history...
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

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

Consider:

if (a > 0)
    b = 42;

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

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

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

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

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

Loading history...
Bug introduced by
The variable c does not seem to be initialized in case m(a) && a = a.call(this)...& "object" == typeof a on line 14167 is true. Are you sure this can never be the case?
Loading history...
Comprehensibility Naming Best Practice introduced by
The variable l already seems to be declared on line 14167. Consider using another variable name or omitting the var keyword.

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

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

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

Loading history...
Coding Style introduced by
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
Coding Style Best Practice introduced by
By convention, constructors like h should be capitalized.
Loading history...
Unused Code introduced by
The variable b seems to be never used. Consider removing it.
Loading history...
Bug introduced by
The variable p seems to be never initialized.
Loading history...
Bug introduced by
The variable f seems to not be initialized for all possible execution paths. Are you sure call handles undefined variables?
Loading history...
Unused Code introduced by
The variable f seems to be never used. Consider removing it.
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...
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...
Coding Style Best Practice introduced by
By convention, constructors like l should be capitalized.
Loading history...
14168
this["wysihtml5"]["tpl"] = this["wysihtml5"]["tpl"] || {};
14169
14170 View Code Duplication
this["wysihtml5"]["tpl"]["blockquote"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14171
  this.compilerInfo = [4,'>= 1.0.0'];
14172
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14173
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14174
14175
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...
14176
  
14177
  var buffer = "", stack1;
14178
  buffer += "btn-"
14179
    + 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...
14180
  return buffer;
14181
  }
14182
14183
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...
14184
  
14185
  
14186
  return " \n      <span class=\"fa fa-quote-left\"></span>\n    ";
14187
  }
14188
14189
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...
14190
  
14191
  
14192
  return "\n      <span class=\"glyphicon glyphicon-quote\"></span>\n    ";
14193
  }
14194
14195
  buffer += "<li>\n  <a class=\"btn ";
14196
  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...
14197
  if(stack1 || stack1 === 0) { buffer += stack1; }
14198
  buffer += " btn-default\" data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"blockquote\" data-wysihtml5-display-format-name=\"false\" tabindex=\"-1\">\n    ";
14199
  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...
14200
  if(stack1 || stack1 === 0) { buffer += stack1; }
14201
  buffer += "\n  </a>\n</li>\n";
14202
  return buffer;
14203
  });
14204
14205
this["wysihtml5"]["tpl"]["color"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14206
  this.compilerInfo = [4,'>= 1.0.0'];
14207
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14208
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14209
14210
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...
14211
  
14212
  var buffer = "", stack1;
14213
  buffer += "btn-"
14214
    + 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...
14215
  return buffer;
14216
  }
14217
14218
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
14219
  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...
14220
  if(stack1 || stack1 === 0) { buffer += stack1; }
14221
  buffer += "\" data-toggle=\"dropdown\" tabindex=\"-1\">\n    <span class=\"current-color\">"
14222
    + 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...
14223
    + "</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\">"
14224
    + 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...
14225
    + "</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\">"
14226
    + 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...
14227
    + "</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\">"
14228
    + 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...
14229
    + "</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\">"
14230
    + 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...
14231
    + "</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\">"
14232
    + 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...
14233
    + "</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\">"
14234
    + 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...
14235
    + "</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\">"
14236
    + 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...
14237
    + "</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\">"
14238
    + 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...
14239
    + "</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\">"
14240
    + 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...
14241
    + "</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\">"
14242
    + 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...
14243
    + "</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\">"
14244
    + 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...
14245
    + "</a></li>\n  </ul>\n</li>\n";
14246
  return buffer;
14247
  });
14248
14249
this["wysihtml5"]["tpl"]["emphasis"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14250
  this.compilerInfo = [4,'>= 1.0.0'];
14251
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14252
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14253
14254
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...
14255
  
14256
  var buffer = "", stack1;
14257
  buffer += "btn-"
14258
    + 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...
14259
  return buffer;
14260
  }
14261
14262
function program3(depth0,data) {
14263
  
14264
  var buffer = "", stack1;
14265
  buffer += "\n    <a class=\"btn ";
14266
  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...
14267
  if(stack1 || stack1 === 0) { buffer += stack1; }
14268
  buffer += " btn-default\" data-wysihtml5-command=\"small\" title=\"CTRL+S\" tabindex=\"-1\">"
14269
    + 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...
14270
    + "</a>\n    ";
14271
  return buffer;
14272
  }
14273
14274
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14275
  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...
14276
  if(stack1 || stack1 === 0) { buffer += stack1; }
14277
  buffer += " btn-default\" data-wysihtml5-command=\"bold\" title=\"CTRL+B\" tabindex=\"-1\">"
14278
    + 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...
14279
    + "</a>\n    <a class=\"btn ";
14280
  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...
14281
  if(stack1 || stack1 === 0) { buffer += stack1; }
14282
  buffer += " btn-default\" data-wysihtml5-command=\"italic\" title=\"CTRL+I\" tabindex=\"-1\">"
14283
    + 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...
14284
    + "</a>\n    <a class=\"btn ";
14285
  stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.toolbar)),stack1 == null || stack1 === false ? stack1 : stack1.size), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
0 ignored issues
show
Comprehensibility introduced by
Usage of the sequence operator is discouraged, since it may lead to obfuscated code.

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

This operator is most often used in for statements.

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

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

var a,b,c;

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

could just as well be written as:

var a,b,c;

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

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

Loading history...
14286
  if(stack1 || stack1 === 0) { buffer += stack1; }
14287
  buffer += " btn-default\" data-wysihtml5-command=\"underline\" title=\"CTRL+U\" tabindex=\"-1\">"
14288
    + 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...
14289
    + "</a>\n    ";
14290
  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...
14291
  if(stack1 || stack1 === 0) { buffer += stack1; }
14292
  buffer += "\n  </div>\n</li>\n";
14293
  return buffer;
14294
  });
14295
14296
this["wysihtml5"]["tpl"]["font-styles"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14297
  this.compilerInfo = [4,'>= 1.0.0'];
14298
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14299
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14300
14301
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...
14302
  
14303
  var buffer = "", stack1;
14304
  buffer += "btn-"
14305
    + 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...
14306
  return buffer;
14307
  }
14308
14309
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...
14310
  
14311
  
14312
  return "\n      <span class=\"fa fa-font\"></span>\n    ";
14313
  }
14314
14315
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...
14316
  
14317
  
14318
  return "\n      <span class=\"glyphicon glyphicon-font\"></span>\n    ";
14319
  }
14320
14321
  buffer += "<li class=\"dropdown\">\n  <a class=\"btn btn-default dropdown-toggle ";
14322
  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...
14323
  if(stack1 || stack1 === 0) { buffer += stack1; }
14324
  buffer += "\" data-toggle=\"dropdown\">\n    ";
14325
  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...
14326
  if(stack1 || stack1 === 0) { buffer += stack1; }
14327
  buffer += "\n    <span class=\"current-font\">"
14328
    + 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...
14329
    + "</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\">"
14330
    + 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...
14331
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h1\" tabindex=\"-1\">"
14332
    + 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...
14333
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h2\" tabindex=\"-1\">"
14334
    + 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...
14335
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h3\" tabindex=\"-1\">"
14336
    + 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...
14337
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h4\" tabindex=\"-1\">"
14338
    + 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...
14339
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h5\" tabindex=\"-1\">"
14340
    + 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...
14341
    + "</a></li>\n    <li><a data-wysihtml5-command=\"formatBlock\" data-wysihtml5-command-value=\"h6\" tabindex=\"-1\">"
14342
    + 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...
14343
    + "</a></li>\n  </ul>\n</li>\n";
14344
  return buffer;
14345
  });
14346
14347 View Code Duplication
this["wysihtml5"]["tpl"]["html"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14348
  this.compilerInfo = [4,'>= 1.0.0'];
14349
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14350
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14351
14352
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...
14353
  
14354
  var buffer = "", stack1;
14355
  buffer += "btn-"
14356
    + 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...
14357
  return buffer;
14358
  }
14359
14360
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...
14361
  
14362
  
14363
  return "\n        <span class=\"fa fa-pencil\"></span>\n      ";
14364
  }
14365
14366
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...
14367
  
14368
  
14369
  return "\n        <span class=\"glyphicon glyphicon-pencil\"></span>\n      ";
14370
  }
14371
14372
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14373
  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...
14374
  if(stack1 || stack1 === 0) { buffer += stack1; }
14375
  buffer += " btn-default\" data-wysihtml5-action=\"change_view\" title=\""
14376
    + 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...
14377
    + "\" tabindex=\"-1\">\n      ";
14378
  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...
14379
  if(stack1 || stack1 === 0) { buffer += stack1; }
14380
  buffer += "\n    </a>\n  </div>\n</li>\n";
14381
  return buffer;
14382
  });
14383
14384 View Code Duplication
this["wysihtml5"]["tpl"]["image"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14385
  this.compilerInfo = [4,'>= 1.0.0'];
14386
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14387
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14388
14389
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...
14390
  
14391
  
14392
  return "modal-sm";
14393
  }
14394
14395
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...
14396
  
14397
  var buffer = "", stack1;
14398
  buffer += "btn-"
14399
    + 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...
14400
  return buffer;
14401
  }
14402
14403
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...
14404
  
14405
  
14406
  return "\n      <span class=\"fa fa-file-image-o\"></span>\n    ";
14407
  }
14408
14409
function program7(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

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

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

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

Loading history...
14410
  
14411
  
14412
  return "\n      <span class=\"glyphicon glyphicon-picture\"></span>\n    ";
14413
  }
14414
14415
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-image-modal modal fade\" data-wysihtml5-dialog=\"insertImage\">\n    <div class=\"modal-dialog ";
14416
  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...
14417
  if(stack1 || stack1 === 0) { buffer += stack1; }
14418
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14419
    + 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...
14420
    + "</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\" data-wysihtml5-dialog-field=\"src\">\n          </div> \n        </div>\n        <div class=\"modal-footer\">\n          <a class=\"btn btn-default\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"cancel\" href=\"#\">"
14421
    + 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...
14422
    + "</a>\n          <a class=\"btn btn-primary\" data-dismiss=\"modal\"  data-wysihtml5-dialog-action=\"save\" href=\"#\">"
14423
    + 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...
14424
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14425
  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...
14426
  if(stack1 || stack1 === 0) { buffer += stack1; }
14427
  buffer += " btn-default\" data-wysihtml5-command=\"insertImage\" title=\""
14428
    + 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...
14429
    + "\" tabindex=\"-1\">\n    ";
14430
  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...
14431
  if(stack1 || stack1 === 0) { buffer += stack1; }
14432
  buffer += "\n  </a>\n</li>\n";
14433
  return buffer;
14434
  });
14435
14436 View Code Duplication
this["wysihtml5"]["tpl"]["link"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
14437
  this.compilerInfo = [4,'>= 1.0.0'];
14438
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14439
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14440
14441
function program1(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter depth0 is not used and could be removed.

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

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

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

Loading history...
14442
  
14443
  
14444
  return "modal-sm";
14445
  }
14446
14447
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...
14448
  
14449
  var buffer = "", stack1;
14450
  buffer += "btn-"
14451
    + 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...
14452
  return buffer;
14453
  }
14454
14455
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...
14456
  
14457
  
14458
  return "\n      <span class=\"fa fa-share-square-o\"></span>\n    ";
14459
  }
14460
14461
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...
14462
  
14463
  
14464
  return "\n      <span class=\"glyphicon glyphicon-share\"></span>\n    ";
14465
  }
14466
14467
  buffer += "<li>\n  <div class=\"bootstrap-wysihtml5-insert-link-modal modal fade\" data-wysihtml5-dialog=\"createLink\">\n    <div class=\"modal-dialog ";
14468
  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...
14469
  if(stack1 || stack1 === 0) { buffer += stack1; }
14470
  buffer += "\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <a class=\"close\" data-dismiss=\"modal\">&times;</a>\n          <h3>"
14471
    + 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...
14472
    + "</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>"
14473
    + 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...
14474
    + "\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=\"#\">"
14475
    + 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...
14476
    + "</a>\n          <a href=\"#\" class=\"btn btn-primary\" data-dismiss=\"modal\" data-wysihtml5-dialog-action=\"save\">"
14477
    + 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...
14478
    + "</a>\n        </div>\n      </div>\n    </div>\n  </div>\n  <a class=\"btn ";
14479
  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...
14480
  if(stack1 || stack1 === 0) { buffer += stack1; }
14481
  buffer += " btn-default\" data-wysihtml5-command=\"createLink\" title=\""
14482
    + 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...
14483
    + "\" tabindex=\"-1\">\n    ";
14484
  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...
14485
  if(stack1 || stack1 === 0) { buffer += stack1; }
14486
  buffer += "\n  </a>\n</li>\n";
14487
  return buffer;
14488
  });
14489
14490
this["wysihtml5"]["tpl"]["lists"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
14491
  this.compilerInfo = [4,'>= 1.0.0'];
14492
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
14493
  var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
14494
14495
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...
14496
  
14497
  var buffer = "", stack1;
14498
  buffer += "btn-"
14499
    + 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...
14500
  return buffer;
14501
  }
14502
14503
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...
14504
  
14505
  
14506
  return "\n      <span class=\"fa fa-list-ul\"></span>\n    ";
14507
  }
14508
14509
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...
14510
  
14511
  
14512
  return "\n      <span class=\"glyphicon glyphicon-list\"></span>\n    ";
14513
  }
14514
14515
function program7(depth0,data) {
0 ignored issues
show
Unused Code introduced by
The parameter data is not used and could be removed.

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

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

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

Loading history...
14516
  
14517
  
14518
  return "\n      <span class=\"fa fa-list-ol\"></span>\n    ";
14519
  }
14520
14521
function program9(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...
14522
  
14523
  
14524
  return "\n      <span class=\"glyphicon glyphicon-th-list\"></span>\n    ";
14525
  }
14526
14527
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...
14528
  
14529
  
14530
  return "\n      <span class=\"fa fa-outdent\"></span>\n    ";
14531
  }
14532
14533
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...
14534
  
14535
  
14536
  return "\n      <span class=\"glyphicon glyphicon-indent-right\"></span>\n    ";
14537
  }
14538
14539
function program15(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...
14540
  
14541
  
14542
  return "\n      <span class=\"fa fa-indent\"></span>\n    ";
14543
  }
14544
14545
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...
14546
  
14547
  
14548
  return "\n      <span class=\"glyphicon glyphicon-indent-left\"></span>\n    ";
14549
  }
14550
14551
  buffer += "<li>\n  <div class=\"btn-group\">\n    <a class=\"btn ";
14552
  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...
14553
  if(stack1 || stack1 === 0) { buffer += stack1; }
14554
  buffer += " btn-default\" data-wysihtml5-command=\"insertUnorderedList\" title=\""
14555
    + 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...
14556
    + "\" tabindex=\"-1\">\n    ";
14557
  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...
14558
  if(stack1 || stack1 === 0) { buffer += stack1; }
14559
  buffer += "\n    </a>\n    <a class=\"btn ";
14560
  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...
14561
  if(stack1 || stack1 === 0) { buffer += stack1; }
14562
  buffer += " btn-default\" data-wysihtml5-command=\"insertOrderedList\" title=\""
14563
    + 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...
14564
    + "\" tabindex=\"-1\">\n    ";
14565
  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...
14566
  if(stack1 || stack1 === 0) { buffer += stack1; }
14567
  buffer += "\n    </a>\n    <a class=\"btn ";
14568
  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...
14569
  if(stack1 || stack1 === 0) { buffer += stack1; }
14570
  buffer += " btn-default\" data-wysihtml5-command=\"Outdent\" title=\""
14571
    + 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...
14572
    + "\" tabindex=\"-1\">\n    ";
14573
  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...
14574
  if(stack1 || stack1 === 0) { buffer += stack1; }
14575
  buffer += "\n    </a>\n    <a class=\"btn ";
14576
  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...
14577
  if(stack1 || stack1 === 0) { buffer += stack1; }
14578
  buffer += " btn-default\" data-wysihtml5-command=\"Indent\" title=\""
14579
    + 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...
14580
    + "\" tabindex=\"-1\">\n    ";
14581
  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...
14582
  if(stack1 || stack1 === 0) { buffer += stack1; }
14583
  buffer += "\n    </a>\n  </div>\n</li>\n";
14584
  return buffer;
14585
  });(function (factory) {
14586
  'use strict';
14587
  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...
14588
    // AMD. Register as an anonymous module.
14589
    define('bootstrap.wysihtml5', ['jquery', 'wysihtml5', 'bootstrap', 'bootstrap.wysihtml5.templates', 'bootstrap.wysihtml5.commands'], factory);
14590
  } else {
14591
    // Browser globals
14592
    factory(jQuery, wysihtml5); // jshint ignore:line
14593
  }
14594
}(function ($, wysihtml5) {
14595
  'use strict';
14596
  var bsWysihtml5 = function($, wysihtml5) {
14597
14598
    var templates = function(key, locale, options) {
14599
      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...
14600
        return wysihtml5.tpl[key]({locale: locale, options: options});
14601
      }
14602
    };
14603
14604
    var Wysihtml5 = function(el, options) {
14605
      this.el = el;
14606
      var toolbarOpts = $.extend(true, {}, defaultOptions, options);
14607
      for(var t in toolbarOpts.customTemplates) {
14608
        if (toolbarOpts.customTemplates.hasOwnProperty(t)) {
14609
          wysihtml5.tpl[t] = toolbarOpts.customTemplates[t];
14610
        }
14611
      }
14612
      this.toolbar = this.createToolbar(el, toolbarOpts);
14613
      this.editor =  this.createEditor(toolbarOpts);
14614
    };
14615
14616
    Wysihtml5.prototype = {
14617
14618
      constructor: Wysihtml5,
14619
14620
      createEditor: function(options) {
14621
        options = options || {};
14622
14623
        // Add the toolbar to a clone of the options object so multiple instances
14624
        // of the WYISYWG don't break because 'toolbar' is already defined
14625
        options = $.extend(true, {}, options);
14626
        options.toolbar = this.toolbar[0];
14627
        
14628
        this.initializeEditor(this.el[0], options);
14629
      },
14630
14631
14632
      initializeEditor: function(el, options) {
14633
        var editor = new wysihtml5.Editor(this.el[0], options);
14634
14635
        editor.on('beforeload', this.syncBootstrapDialogEvents);
14636
        editor.on('beforeload', this.loadParserRules);
14637
14638
        // #30 - body is in IE 10 not created by default, which leads to nullpointer
14639
        // 2014/02/13 - adapted to wysihtml5-0.4, does not work in IE
14640
        if(editor.composer.editableArea.contentDocument) {
14641
          this.addMoreShortcuts(editor, 
14642
                                editor.composer.editableArea.contentDocument.body || editor.composer.editableArea.contentDocument, 
14643
                                options.shortcuts);
14644
        } else {
14645
          this.addMoreShortcuts(editor, editor.composer.editableArea, options.shortcuts);    
14646
        }
14647
14648
        if(options && options.events) {
14649
          for(var eventName in options.events) {
14650
            if (options.events.hasOwnProperty(eventName)) {
14651
              editor.on(eventName, options.events[eventName]);
14652
            }
14653
          }
14654
        }
14655
14656
        return editor;
14657
      },
14658
14659
      loadParserRules: function() {
14660
        if($.type(this.config.parserRules) === 'string') {
14661
          $.ajax({
14662
            dataType: 'json',
14663
            url: this.config.parserRules,
14664
            context: this,
14665
            error: function (jqXHR, textStatus, errorThrown) {
14666
              console.log(errorThrown);
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...
14667
            },
14668
            success: function (parserRules) {
14669
              this.config.parserRules = parserRules;
14670
              console.log('parserrules loaded');
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...
14671
            }
14672
          });
14673
        }
14674
14675
        if(this.config.pasteParserRulesets && $.type(this.config.pasteParserRulesets) === 'string') {
14676
          $.ajax({
14677
            dataType: 'json',
14678
            url: this.config.pasteParserRulesets,
14679
            context: this,
14680
            error: function (jqXHR, textStatus, errorThrown) {
14681
              console.log(errorThrown);
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...
14682
            },
14683
            success: function (pasteParserRulesets) {
14684
              this.config.pasteParserRulesets = pasteParserRulesets;
14685
            }
14686
          });
14687
        }
14688
      },
14689
14690
      //sync wysihtml5 events for dialogs with bootstrap events
14691
      syncBootstrapDialogEvents: function() {
14692
        var editor = this;
14693
        $.map(this.toolbar.commandMapping, function(value) {
14694
          return [value];
14695
        }).filter(function(commandObj) {
14696
          return commandObj.dialog;
14697
        }).map(function(commandObj) {
14698
          return commandObj.dialog;
14699
        }).forEach(function(dialog) {
14700
          dialog.on('show', function() {
14701
            $(this.container).modal('show');
14702
          });
14703
          dialog.on('hide', function() {
14704
            $(this.container).modal('hide');
14705
            setTimeout(editor.composer.focus, 0);
14706
          });
14707
          $(dialog.container).on('shown.bs.modal', function () {
14708
            $(this).find('input, select, textarea').first().focus();
14709
          });
14710
        });
14711
        this.on('change_view', function() {
14712
          $(this.toolbar.container.children).find('a.btn').not('[data-wysihtml5-action="change_view"]').toggleClass('disabled');
14713
        });
14714
      },
14715
14716
      createToolbar: function(el, options) {
14717
        var self = this;
14718
        var toolbar = $('<ul/>', {
14719
          'class' : 'wysihtml5-toolbar',
14720
          'style': 'display:none'
14721
        });
14722
        var culture = options.locale || defaultOptions.locale || 'en';
14723
        if(!locale.hasOwnProperty(culture)) {
14724
          console.debug('Locale \'' + culture + '\' not found. Available locales are: ' + Object.keys(locale) + '. Falling back to \'en\'.');
14725
          culture = 'en';
14726
        }
14727
        var localeObject = $.extend(true, {}, locale.en, locale[culture]);
14728
        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...
14729
          if(options.toolbar[key]) {
14730
            toolbar.append(templates(key, localeObject, options));
14731
          }
14732
        }
14733
14734
        toolbar.find('a[data-wysihtml5-command="formatBlock"]').click(function(e) {
14735
          var target = e.delegateTarget || e.target || e.srcElement,
14736
          el = $(target),
14737
          showformat = el.data('wysihtml5-display-format-name'),
14738
          formatname = el.data('wysihtml5-format-name') || el.html();
14739
          if(showformat === undefined || showformat === 'true') {
14740
            self.toolbar.find('.current-font').text(formatname);
14741
          }
14742
        });
14743
14744
        toolbar.find('a[data-wysihtml5-command="foreColor"]').click(function(e) {
14745
          var target = e.target || e.srcElement;
14746
          var el = $(target);
14747
          self.toolbar.find('.current-color').text(el.html());
14748
        });
14749
14750
        this.el.before(toolbar);
14751
14752
        return toolbar;
14753
      },
14754
14755
      addMoreShortcuts: function(editor, el, shortcuts) {
14756
        /* some additional shortcuts */
14757
        wysihtml5.dom.observe(el, 'keydown', function(event) {
14758
          var keyCode  = event.keyCode,
14759
          command  = shortcuts[keyCode];
14760
          if ((event.ctrlKey || event.metaKey || event.altKey) && command && wysihtml5.commands[command]) {
14761
            var commandObj = editor.toolbar.commandMapping[command + ':null'];
14762
            if (commandObj && commandObj.dialog && !commandObj.state) {
14763
              commandObj.dialog.show();
14764
            } else {
14765
              wysihtml5.commands[command].exec(editor.composer, command);
14766
            }
14767
            event.preventDefault();
14768
          }
14769
        });
14770
      }
14771
    };
14772
14773
    // these define our public api
14774
    var methods = {
14775
      resetDefaults: function() {
14776
        $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache);
14777
      },
14778
      bypassDefaults: function(options) {
14779
        return this.each(function () {
14780
          var $this = $(this);
14781
          $this.data('wysihtml5', new Wysihtml5($this, options));
14782
        });
14783
      },
14784
      shallowExtend: function (options) {
14785
        var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}, $(this).data());
14786
        var that = this;
14787
        return methods.bypassDefaults.apply(that, [settings]);
14788
      },
14789
      deepExtend: function(options) {
14790
        var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {});
14791
        var that = this;
14792
        return methods.bypassDefaults.apply(that, [settings]);
14793
      },
14794
      init: function(options) {
14795
        var that = this;
14796
        return methods.shallowExtend.apply(that, [options]);
14797
      }
14798
    };
14799
14800
    $.fn.wysihtml5 = function ( method ) {
14801
      if ( methods[method] ) {
14802
        return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
14803
      } else if ( typeof method === 'object' || ! method ) {
14804
        return methods.init.apply( this, arguments );
14805
      } else {
14806
        $.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...
14807
      }    
14808
    };
14809
14810
    $.fn.wysihtml5.Constructor = Wysihtml5;
14811
14812
    var defaultOptions = $.fn.wysihtml5.defaultOptions = {
14813
      toolbar: {
14814
        'font-styles': true,
14815
        'color': false,
14816
        'emphasis': {
14817
          'small': true
14818
        },
14819
        'blockquote': true,
14820
        'lists': true,
14821
        'html': false,
14822
        'link': true,
14823
        'image': true,
14824
        'smallmodals': false
14825
      },
14826
      useLineBreaks: false,
14827
      parserRules: {
14828
        classes: {
14829
          'wysiwyg-color-silver' : 1,
14830
          'wysiwyg-color-gray' : 1,
14831
          'wysiwyg-color-white' : 1,
14832
          'wysiwyg-color-maroon' : 1,
14833
          'wysiwyg-color-red' : 1,
14834
          'wysiwyg-color-purple' : 1,
14835
          'wysiwyg-color-fuchsia' : 1,
14836
          'wysiwyg-color-green' : 1,
14837
          'wysiwyg-color-lime' : 1,
14838
          'wysiwyg-color-olive' : 1,
14839
          'wysiwyg-color-yellow' : 1,
14840
          'wysiwyg-color-navy' : 1,
14841
          'wysiwyg-color-blue' : 1,
14842
          'wysiwyg-color-teal' : 1,
14843
          'wysiwyg-color-aqua' : 1,
14844
          'wysiwyg-color-orange' : 1
14845
        },
14846
        tags: {
14847
          'b':  {},
14848
          'i':  {},
14849
          'strong': {},
14850
          'em': {},
14851
          'p': {},
14852
          'br': {},
14853
          'ol': {},
14854
          'ul': {},
14855
          'li': {},
14856
          'h1': {},
14857
          'h2': {},
14858
          'h3': {},
14859
          'h4': {},
14860
          'h5': {},
14861
          'h6': {},
14862
          'blockquote': {},
14863
          'u': 1,
14864
          'img': {
14865
            'check_attributes': {
14866
              'width': 'numbers',
14867
              'alt': 'alt',
14868
              'src': 'url',
14869
              'height': 'numbers'
14870
            }
14871
          },
14872
          'a':  {
14873
            'check_attributes': {
14874
              'href': 'url'
14875
            },
14876
            'set_attributes': {
14877
              'target': '_blank',
14878
              'rel': 'nofollow'
14879
            }
14880
          },
14881
          'span': 1,
14882
          'div': 1,
14883
          'small': 1,
14884
          'code': 1,
14885
          'pre': 1
14886
        }
14887
      },
14888
      locale: 'en',
14889
      shortcuts: {
14890
        '83': 'small',// S
14891
        '75': 'createLink'// K
14892
      }
14893
    };
14894
14895
    if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') {
14896
      $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions);
14897
    }
14898
14899
    var locale = $.fn.wysihtml5.locale = {};
14900
  };
14901
  bsWysihtml5($, wysihtml5);
14902
}));
14903
(function(wysihtml5) {
14904
  wysihtml5.commands.small = {
14905
    exec: function(composer, command) {
14906
      return wysihtml5.commands.formatInline.exec(composer, command, "small");
14907
    },
14908
14909
    state: function(composer, command) {
14910
      return wysihtml5.commands.formatInline.state(composer, command, "small");
14911
    }
14912
  };
14913
})(wysihtml5);
14914
14915
/**
14916
 * English translation for bootstrap-wysihtml5
14917
 */
14918
(function (factory) {
14919
    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...
14920
        // AMD. Register as an anonymous module.
14921
        define('bootstrap.wysihtml5.en-US', ['jquery', 'bootstrap.wysihtml5'], factory);
14922
    } else {
14923
        // Browser globals
14924
        factory(jQuery);
14925
    }
14926
}(function ($) {
14927
  $.fn.wysihtml5.locale.en = $.fn.wysihtml5.locale['en-US'] = {
14928
    font_styles: {
14929
      normal: 'Normal text',
14930
      h1: 'Heading 1',
14931
      h2: 'Heading 2',
14932
      h3: 'Heading 3',
14933
      h4: 'Heading 4',
14934
      h5: 'Heading 5',
14935
      h6: 'Heading 6'
14936
    },
14937
    emphasis: {
14938
      bold: 'Bold',
14939
      italic: 'Italic',
14940
      underline: 'Underline',
14941
      small: 'Small'
14942
    },
14943
    lists: {
14944
      unordered: 'Unordered list',
14945
      ordered: 'Ordered list',
14946
      outdent: 'Outdent',
14947
      indent: 'Indent'
14948
    },
14949
    link: {
14950
      insert: 'Insert link',
14951
      cancel: 'Cancel',
14952
      target: 'Open link in new window'
14953
    },
14954
    image: {
14955
      insert: 'Insert image',
14956
      cancel: 'Cancel'
14957
    },
14958
    html: {
14959
      edit: 'Edit HTML'
14960
    },
14961
    colours: {
14962
      black: 'Black',
14963
      silver: 'Silver',
14964
      gray: 'Grey',
14965
      maroon: 'Maroon',
14966
      red: 'Red',
14967
      purple: 'Purple',
14968
      green: 'Green',
14969
      olive: 'Olive',
14970
      navy: 'Navy',
14971
      blue: 'Blue',
14972
      orange: 'Orange'
14973
    }
14974
  };
14975
}));
14976