wysihtml-rails 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/wysihtml/rails/version.rb +1 -1
- data/vendor/assets/javascripts/wysihtml-toolbar.js +374 -125
- data/vendor/assets/javascripts/wysihtml.js +374 -125
- metadata +81 -56
- checksums.yaml +0 -7
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license wysihtml v0.5.
|
2
|
+
* @license wysihtml v0.5.3
|
3
3
|
* https://github.com/Voog/wysihtml
|
4
4
|
*
|
5
5
|
* Author: Christopher Blum (https://github.com/tiff)
|
@@ -10,7 +10,7 @@
|
|
10
10
|
*
|
11
11
|
*/
|
12
12
|
var wysihtml5 = {
|
13
|
-
version: "0.5.
|
13
|
+
version: "0.5.3",
|
14
14
|
|
15
15
|
// namespaces
|
16
16
|
commands: {},
|
@@ -24,6 +24,8 @@ var wysihtml5 = {
|
|
24
24
|
INVISIBLE_SPACE: "\uFEFF",
|
25
25
|
INVISIBLE_SPACE_REG_EXP: /\uFEFF/g,
|
26
26
|
|
27
|
+
VOID_ELEMENTS: "area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr",
|
28
|
+
|
27
29
|
EMPTY_FUNCTION: function() {},
|
28
30
|
|
29
31
|
ELEMENT_NODE: 1,
|
@@ -421,7 +423,19 @@ var wysihtml5 = {
|
|
421
423
|
return all;
|
422
424
|
};
|
423
425
|
|
426
|
+
var isInDom = function(node) {
|
427
|
+
var doc = node.ownerDocument,
|
428
|
+
n = node;
|
424
429
|
|
430
|
+
do {
|
431
|
+
if (n === doc) {
|
432
|
+
return true;
|
433
|
+
}
|
434
|
+
n = n.parentNode;
|
435
|
+
} while(n);
|
436
|
+
|
437
|
+
return false;
|
438
|
+
};
|
425
439
|
|
426
440
|
var normalizeFix = function() {
|
427
441
|
var f = Node.prototype.normalize;
|
@@ -482,7 +496,7 @@ var wysihtml5 = {
|
|
482
496
|
aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement);
|
483
497
|
}
|
484
498
|
|
485
|
-
if (anode && anode.parentNode && fnode && fnode.parentNode) {
|
499
|
+
if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) {
|
486
500
|
r.setStart(anode, aoffset);
|
487
501
|
r.setEnd(fnode, foffset);
|
488
502
|
s.removeAllRanges();
|
@@ -504,6 +518,31 @@ var wysihtml5 = {
|
|
504
518
|
} else {
|
505
519
|
F();
|
506
520
|
}
|
521
|
+
|
522
|
+
// CustomEvent for ie9 and up
|
523
|
+
function nativeCustomEventSupported() {
|
524
|
+
try {
|
525
|
+
var p = new CustomEvent('cat', {detail: {foo: 'bar'}});
|
526
|
+
return 'cat' === p.type && 'bar' === p.detail.foo;
|
527
|
+
} catch (e) {}
|
528
|
+
return false;
|
529
|
+
}
|
530
|
+
var customEventSupported = nativeCustomEventSupported();
|
531
|
+
|
532
|
+
// Polyfills CustomEvent object for IE9 and up
|
533
|
+
(function() {
|
534
|
+
if (!customEventSupported && "CustomEvent" in window) {
|
535
|
+
function CustomEvent(event, params) {
|
536
|
+
params = params || {bubbles: false, cancelable: false, detail: undefined};
|
537
|
+
var evt = doc.createEvent('CustomEvent');
|
538
|
+
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
539
|
+
return evt;
|
540
|
+
}
|
541
|
+
CustomEvent.prototype = win.Event.prototype;
|
542
|
+
win.CustomEvent = CustomEvent;
|
543
|
+
customEventSupported = true;
|
544
|
+
}
|
545
|
+
})();
|
507
546
|
};
|
508
547
|
|
509
548
|
wysihtml5.polyfills(window, document);
|
@@ -6677,10 +6716,11 @@ wysihtml5.browser = (function() {
|
|
6677
6716
|
var userAgent = navigator.userAgent,
|
6678
6717
|
testElement = document.createElement("div"),
|
6679
6718
|
// Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
|
6680
|
-
|
6681
|
-
|
6682
|
-
|
6683
|
-
|
6719
|
+
// We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers
|
6720
|
+
isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(),
|
6721
|
+
isWebKit = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(),
|
6722
|
+
isChrome = userAgent.indexOf("Chrome/") !== -1 && !isIE(),
|
6723
|
+
isOpera = userAgent.indexOf("Opera/") !== -1 && !isIE();
|
6684
6724
|
|
6685
6725
|
function iosVersion(userAgent) {
|
6686
6726
|
return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
|
@@ -6850,14 +6890,15 @@ wysihtml5.browser = (function() {
|
|
6850
6890
|
*/
|
6851
6891
|
supportsCommand: (function() {
|
6852
6892
|
// Following commands are supported but contain bugs in some browsers
|
6893
|
+
// TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly
|
6853
6894
|
var buggyCommands = {
|
6854
6895
|
// formatBlock fails with some tags (eg. <blockquote>)
|
6855
6896
|
"formatBlock": isIE(10, "<="),
|
6856
6897
|
// When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
|
6857
6898
|
// converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
|
6858
6899
|
// IE and Opera act a bit different here as they convert the entire content of the current block element into a list
|
6859
|
-
"insertUnorderedList": isIE(
|
6860
|
-
"insertOrderedList": isIE(
|
6900
|
+
"insertUnorderedList": isIE(),
|
6901
|
+
"insertOrderedList": isIE()
|
6861
6902
|
};
|
6862
6903
|
|
6863
6904
|
// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
|
@@ -7014,6 +7055,11 @@ wysihtml5.browser = (function() {
|
|
7014
7055
|
return isIE();
|
7015
7056
|
},
|
7016
7057
|
|
7058
|
+
/* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */
|
7059
|
+
hasLiDeletingProblem: function() {
|
7060
|
+
return isIE();
|
7061
|
+
},
|
7062
|
+
|
7017
7063
|
hasUndoInContextMenu: function() {
|
7018
7064
|
return isGecko || isChrome || isOpera;
|
7019
7065
|
},
|
@@ -7047,6 +7093,12 @@ wysihtml5.browser = (function() {
|
|
7047
7093
|
return isWebKit;
|
7048
7094
|
},
|
7049
7095
|
|
7096
|
+
// In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element
|
7097
|
+
// when startContainer is element.
|
7098
|
+
hasCaretBlockElementIssue: function() {
|
7099
|
+
return isWebKit;
|
7100
|
+
},
|
7101
|
+
|
7050
7102
|
supportsMutationEvents: function() {
|
7051
7103
|
return ("MutationEvent" in window);
|
7052
7104
|
},
|
@@ -7911,6 +7963,20 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
7911
7963
|
}
|
7912
7964
|
}
|
7913
7965
|
return isVisible;
|
7966
|
+
},
|
7967
|
+
lineBreak: function() {
|
7968
|
+
return node && node.nodeType === 1 && node.nodeName === "BR";
|
7969
|
+
},
|
7970
|
+
block: function() {
|
7971
|
+
return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block";
|
7972
|
+
},
|
7973
|
+
// Void elements are elemens that can not have content
|
7974
|
+
// In most cases browsers should solve the cases for you when you try to insert content into those,
|
7975
|
+
// but IE does not and it is not nice to do so anyway.
|
7976
|
+
voidElement: function() {
|
7977
|
+
return wysihtml5.dom.domNode(node).test({
|
7978
|
+
query: wysihtml5.VOID_ELEMENTS
|
7979
|
+
});
|
7914
7980
|
}
|
7915
7981
|
},
|
7916
7982
|
|
@@ -8068,6 +8134,29 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
8068
8134
|
}
|
8069
8135
|
},
|
8070
8136
|
|
8137
|
+
transferContentTo: function(targetNode, removeOldWrapper) {
|
8138
|
+
if (node.nodeType === 1) {
|
8139
|
+
if (wysihtml5.dom.domNode(targetNode).is.voidElement()) {
|
8140
|
+
while (node.firstChild) {
|
8141
|
+
targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling);
|
8142
|
+
}
|
8143
|
+
} else {
|
8144
|
+
while (node.firstChild) {
|
8145
|
+
targetNode.appendChild(node.firstChild);
|
8146
|
+
}
|
8147
|
+
}
|
8148
|
+
if (removeOldWrapper) {
|
8149
|
+
node.parentNode.removeChild(node);
|
8150
|
+
}
|
8151
|
+
} else if (node.nodeType === 3 || node.nodeType === 8){
|
8152
|
+
if (wysihtml5.dom.domNode(targetNode).is.voidElement()) {
|
8153
|
+
targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
|
8154
|
+
} else {
|
8155
|
+
targetNode.appendChild(node);
|
8156
|
+
}
|
8157
|
+
}
|
8158
|
+
},
|
8159
|
+
|
8071
8160
|
/*
|
8072
8161
|
Tests a node against properties, and returns true if matches.
|
8073
8162
|
Tests on principle that all properties defined must have at least one match.
|
@@ -9541,17 +9630,10 @@ wysihtml5.dom.replaceWithChildNodes = function(node) {
|
|
9541
9630
|
return;
|
9542
9631
|
}
|
9543
9632
|
|
9544
|
-
if (!node.firstChild) {
|
9545
|
-
node.parentNode.removeChild(node);
|
9546
|
-
return;
|
9547
|
-
}
|
9548
|
-
|
9549
|
-
var fragment = node.ownerDocument.createDocumentFragment();
|
9550
9633
|
while (node.firstChild) {
|
9551
|
-
|
9634
|
+
node.parentNode.insertBefore(node.firstChild, node);
|
9552
9635
|
}
|
9553
|
-
node.parentNode.
|
9554
|
-
node = fragment = null;
|
9636
|
+
node.parentNode.removeChild(node);
|
9555
9637
|
};
|
9556
9638
|
;/**
|
9557
9639
|
* Unwraps an unordered/ordered list
|
@@ -12096,6 +12178,43 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12096
12178
|
return (ret !== this.contain) ? ret : false;
|
12097
12179
|
},
|
12098
12180
|
|
12181
|
+
// Gather info about caret location (caret node, previous and next node)
|
12182
|
+
getNodesNearCaret: function() {
|
12183
|
+
if (!this.isCollapsed()) {
|
12184
|
+
throw "Selection must be caret when using selection.getNodesNearCaret()";
|
12185
|
+
}
|
12186
|
+
|
12187
|
+
var r = this.getOwnRanges(),
|
12188
|
+
caretNode, prevNode, nextNode, offset;
|
12189
|
+
|
12190
|
+
if (r && r.length > 0) {
|
12191
|
+
if (r[0].startContainer.nodeType === 1) {
|
12192
|
+
caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1];
|
12193
|
+
if (!caretNode && r[0].startOffset === 0) {
|
12194
|
+
// Is first position before all nodes
|
12195
|
+
nextNode = r[0].startContainer.childNodes[0];
|
12196
|
+
} else if (caretNode) {
|
12197
|
+
prevNode = caretNode.previousSibling;
|
12198
|
+
nextNode = caretNode.nextSibling;
|
12199
|
+
}
|
12200
|
+
} else {
|
12201
|
+
caretNode = r[0].startContainer;
|
12202
|
+
prevNode = caretNode.previousSibling;
|
12203
|
+
nextNode = caretNode.nextSibling;
|
12204
|
+
offset = r[0].startOffset;
|
12205
|
+
}
|
12206
|
+
|
12207
|
+
return {
|
12208
|
+
"caretNode": caretNode,
|
12209
|
+
"prevNode": prevNode,
|
12210
|
+
"nextNode": nextNode,
|
12211
|
+
"textOffset": offset
|
12212
|
+
};
|
12213
|
+
}
|
12214
|
+
|
12215
|
+
return null;
|
12216
|
+
},
|
12217
|
+
|
12099
12218
|
getSelectionParentsByTag: function(tagName) {
|
12100
12219
|
var nodes = this.getSelectedOwnNodes(),
|
12101
12220
|
curEl, parents = [];
|
@@ -12167,6 +12286,11 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12167
12286
|
startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset,
|
12168
12287
|
rng = this.createRange(), endNode, inTmpCaret;
|
12169
12288
|
|
12289
|
+
// If start is textnode and all is whitespace before caret. Set start offset to 0
|
12290
|
+
if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) {
|
12291
|
+
startOffset = 0;
|
12292
|
+
}
|
12293
|
+
|
12170
12294
|
// Escape temproray helper nodes if selection in them
|
12171
12295
|
inTmpCaret = wysihtml5.dom.getParentElement(startNode, { query: '._wysihtml5-temp-caret-fix' }, 1);
|
12172
12296
|
if (inTmpCaret) {
|
@@ -12486,43 +12610,6 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12486
12610
|
return nodes;
|
12487
12611
|
},
|
12488
12612
|
|
12489
|
-
deblockAndSurround: function(nodeOptions) {
|
12490
|
-
var tempElement = this.doc.createElement('div'),
|
12491
|
-
range = rangy.createRange(this.doc),
|
12492
|
-
tempDivElements,
|
12493
|
-
tempElements,
|
12494
|
-
firstChild;
|
12495
|
-
|
12496
|
-
tempElement.className = nodeOptions.className;
|
12497
|
-
|
12498
|
-
this.composer.commands.exec("formatBlock", nodeOptions);
|
12499
|
-
tempDivElements = this.contain.querySelectorAll("." + nodeOptions.className);
|
12500
|
-
if (tempDivElements[0]) {
|
12501
|
-
tempDivElements[0].parentNode.insertBefore(tempElement, tempDivElements[0]);
|
12502
|
-
|
12503
|
-
range.setStartBefore(tempDivElements[0]);
|
12504
|
-
range.setEndAfter(tempDivElements[tempDivElements.length - 1]);
|
12505
|
-
tempElements = range.extractContents();
|
12506
|
-
|
12507
|
-
while (tempElements.firstChild) {
|
12508
|
-
firstChild = tempElements.firstChild;
|
12509
|
-
if (firstChild.nodeType == 1 && wysihtml5.dom.hasClass(firstChild, nodeOptions.className)) {
|
12510
|
-
while (firstChild.firstChild) {
|
12511
|
-
tempElement.appendChild(firstChild.firstChild);
|
12512
|
-
}
|
12513
|
-
if (firstChild.nodeName !== "BR") { tempElement.appendChild(this.doc.createElement('br')); }
|
12514
|
-
tempElements.removeChild(firstChild);
|
12515
|
-
} else {
|
12516
|
-
tempElement.appendChild(firstChild);
|
12517
|
-
}
|
12518
|
-
}
|
12519
|
-
} else {
|
12520
|
-
tempElement = null;
|
12521
|
-
}
|
12522
|
-
|
12523
|
-
return tempElement;
|
12524
|
-
},
|
12525
|
-
|
12526
12613
|
/**
|
12527
12614
|
* Scroll the current caret position into the view
|
12528
12615
|
* FIXME: This is a bit hacky, there might be a smarter way of doing this
|
@@ -14500,20 +14587,55 @@ wysihtml5.Commands = Base.extend(
|
|
14500
14587
|
}
|
14501
14588
|
}
|
14502
14589
|
|
14590
|
+
var isWhitespaceBefore = function (textNode, offset) {
|
14591
|
+
var str = textNode.data ? textNode.data.slice(0, offset) : "";
|
14592
|
+
return (/^\s*$/).test(str);
|
14593
|
+
}
|
14594
|
+
|
14595
|
+
var isWhitespaceAfter = function (textNode, offset) {
|
14596
|
+
var str = textNode.data ? textNode.data.slice(offset) : "";
|
14597
|
+
return (/^\s*$/).test(str);
|
14598
|
+
}
|
14599
|
+
|
14600
|
+
var trimBlankTextsAndBreaks = function(fragment) {
|
14601
|
+
if (fragment) {
|
14602
|
+
while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) {
|
14603
|
+
fragment.removeChild(fragment.firstChild);
|
14604
|
+
}
|
14605
|
+
|
14606
|
+
while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) {
|
14607
|
+
fragment.removeChild(fragment.lastChild);
|
14608
|
+
}
|
14609
|
+
|
14610
|
+
if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
|
14611
|
+
fragment.removeChild(fragment.firstChild);
|
14612
|
+
}
|
14613
|
+
|
14614
|
+
if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
|
14615
|
+
fragment.removeChild(fragment.lastChild);
|
14616
|
+
}
|
14617
|
+
}
|
14618
|
+
}
|
14619
|
+
|
14503
14620
|
// Wrap the range with a block level element
|
14504
14621
|
// If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
|
14505
14622
|
function wrapRangeWithElement(range, options, closestBlockName, composer) {
|
14506
14623
|
var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
|
14507
14624
|
r = range.cloneRange(),
|
14508
14625
|
rangeStartContainer = r.startContainer,
|
14509
|
-
|
14510
|
-
|
14626
|
+
startNode = getRangeNode(r.startContainer, r.startOffset),
|
14627
|
+
endNode = getRangeNode(r.endContainer, r.endOffset),
|
14628
|
+
prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode : wysihtml5.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
14629
|
+
nextNode = ((r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] === endNode) || (r.endContainer === endNode && endNode.nodeType === 3 && !isWhitespaceAfter(endNode, r.endOffset))) ? endNode : wysihtml5.dom.domNode(getRangeNode(r.endContainer, r.endOffset)).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
14511
14630
|
content = r.extractContents(),
|
14512
14631
|
fragment = composer.doc.createDocumentFragment(),
|
14513
14632
|
similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
|
14514
14633
|
splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
|
14515
14634
|
firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
|
14516
|
-
wrapper, blocks, children
|
14635
|
+
wrapper, blocks, children,
|
14636
|
+
firstc, lastC;
|
14637
|
+
|
14638
|
+
trimBlankTextsAndBreaks(content);
|
14517
14639
|
|
14518
14640
|
if (options && options.nodeName === "BLOCKQUOTE") {
|
14519
14641
|
|
@@ -14641,6 +14763,29 @@ wysihtml5.Commands = Base.extend(
|
|
14641
14763
|
return options;
|
14642
14764
|
}
|
14643
14765
|
|
14766
|
+
function caretIsOnEmptyLine(composer) {
|
14767
|
+
var caretInfo;
|
14768
|
+
if (composer.selection.isCollapsed()) {
|
14769
|
+
caretInfo = composer.selection.getNodesNearCaret();
|
14770
|
+
if (caretInfo && caretInfo.caretNode) {
|
14771
|
+
if (
|
14772
|
+
// caret is allready breaknode
|
14773
|
+
wysihtml5.dom.domNode(caretInfo.caretNode).is.lineBreak() ||
|
14774
|
+
// caret is textnode
|
14775
|
+
(caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml5.dom.domNode(caretInfo.prevNode).is.lineBreak())) ||
|
14776
|
+
// Caret is temprorary rangy selection marker
|
14777
|
+
(caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') &&
|
14778
|
+
(!caretInfo.prevNode || wysihtml5.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml5.dom.domNode(caretInfo.prevNode).is.block()) &&
|
14779
|
+
(!caretInfo.nextNode || wysihtml5.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml5.dom.domNode(caretInfo.nextNode).is.block())
|
14780
|
+
)
|
14781
|
+
) {
|
14782
|
+
return true;
|
14783
|
+
}
|
14784
|
+
}
|
14785
|
+
}
|
14786
|
+
return false;
|
14787
|
+
}
|
14788
|
+
|
14644
14789
|
wysihtml5.commands.formatBlock = {
|
14645
14790
|
exec: function(composer, command, options) {
|
14646
14791
|
options = parseOptions(options);
|
@@ -14663,7 +14808,11 @@ wysihtml5.Commands = Base.extend(
|
|
14663
14808
|
// If selection is caret expand it to cover nearest suitable block element or row if none found
|
14664
14809
|
if (composer.selection.isCollapsed()) {
|
14665
14810
|
bookmark = rangy.saveSelection(composer.win);
|
14666
|
-
|
14811
|
+
if (caretIsOnEmptyLine(composer)) {
|
14812
|
+
composer.selection.selectLine();
|
14813
|
+
} else {
|
14814
|
+
expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
|
14815
|
+
}
|
14667
14816
|
}
|
14668
14817
|
if (options) {
|
14669
14818
|
newBlockElements = formatSelection("apply", composer, options);
|
@@ -15719,20 +15868,19 @@ wysihtml5.Commands = Base.extend(
|
|
15719
15868
|
};
|
15720
15869
|
|
15721
15870
|
var createListFallback = function(nodeName, composer) {
|
15722
|
-
var sel;
|
15723
|
-
|
15724
|
-
if (!composer.selection.isCollapsed()) {
|
15725
|
-
sel = rangy.saveSelection(composer.win);
|
15726
|
-
}
|
15871
|
+
var sel = rangy.saveSelection(composer.win);
|
15727
15872
|
|
15728
15873
|
// Fallback for Create list
|
15729
15874
|
var tempClassName = "_wysihtml5-temp-" + new Date().getTime(),
|
15730
|
-
tempElement = composer.selection.deblockAndSurround({
|
15731
|
-
"nodeName": "div",
|
15732
|
-
"className": tempClassName
|
15733
|
-
}),
|
15734
15875
|
isEmpty, list;
|
15735
15876
|
|
15877
|
+
composer.commands.exec("formatBlock", {
|
15878
|
+
"nodeName": "div",
|
15879
|
+
"className": tempClassName
|
15880
|
+
});
|
15881
|
+
|
15882
|
+
var tempElement = composer.element.querySelector("." + tempClassName);
|
15883
|
+
|
15736
15884
|
// This space causes new lists to never break on enter
|
15737
15885
|
var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
|
15738
15886
|
tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml5.INVISIBLE_SPACE_REG_EXP, "");
|
@@ -16946,8 +17094,11 @@ wysihtml5.views.View = Base.extend(
|
|
16946
17094
|
function adjust(selectedNode) {
|
16947
17095
|
var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2);
|
16948
17096
|
if (parentElement && dom.contains(that.element, parentElement)) {
|
16949
|
-
that.selection.
|
17097
|
+
that.selection.executeAndRestoreRangy(function() {
|
16950
17098
|
if (that.config.useLineBreaks) {
|
17099
|
+
if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) {
|
17100
|
+
parentElement.appendChild(that.doc.createElement('br'));
|
17101
|
+
}
|
16951
17102
|
dom.replaceWithChildNodes(parentElement);
|
16952
17103
|
} else if (parentElement.nodeName !== "P") {
|
16953
17104
|
dom.renameElement(parentElement, "p");
|
@@ -16956,18 +17107,21 @@ wysihtml5.views.View = Base.extend(
|
|
16956
17107
|
}
|
16957
17108
|
}
|
16958
17109
|
|
17110
|
+
// Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph
|
16959
17111
|
if (!this.config.useLineBreaks) {
|
16960
|
-
dom.observe(this.element, ["focus"
|
17112
|
+
dom.observe(this.element, ["focus"], function() {
|
16961
17113
|
if (that.isEmpty()) {
|
16962
|
-
|
16963
|
-
|
16964
|
-
|
16965
|
-
|
16966
|
-
|
16967
|
-
|
16968
|
-
|
16969
|
-
|
16970
|
-
|
17114
|
+
setTimeout(function() {
|
17115
|
+
var paragraph = that.doc.createElement("P");
|
17116
|
+
that.element.innerHTML = "";
|
17117
|
+
that.element.appendChild(paragraph);
|
17118
|
+
if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
|
17119
|
+
paragraph.innerHTML = "<br>";
|
17120
|
+
that.selection.setBefore(paragraph.firstChild);
|
17121
|
+
} else {
|
17122
|
+
that.selection.selectNode(paragraph, true);
|
17123
|
+
}
|
17124
|
+
}, 0);
|
16971
17125
|
}
|
16972
17126
|
});
|
16973
17127
|
}
|
@@ -16975,7 +17129,7 @@ wysihtml5.views.View = Base.extend(
|
|
16975
17129
|
dom.observe(this.element, "keydown", function(event) {
|
16976
17130
|
var keyCode = event.keyCode;
|
16977
17131
|
|
16978
|
-
if (event.shiftKey) {
|
17132
|
+
if (event.shiftKey || event.ctrlKey || event.defaultPrevented) {
|
16979
17133
|
return;
|
16980
17134
|
}
|
16981
17135
|
|
@@ -17007,11 +17161,9 @@ wysihtml5.views.View = Base.extend(
|
|
17007
17161
|
}, 0);
|
17008
17162
|
return;
|
17009
17163
|
}
|
17010
|
-
|
17011
17164
|
if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
|
17012
17165
|
event.preventDefault();
|
17013
17166
|
that.commands.exec("insertLineBreak");
|
17014
|
-
|
17015
17167
|
}
|
17016
17168
|
});
|
17017
17169
|
}
|
@@ -17228,6 +17380,7 @@ wysihtml5.views.View = Base.extend(
|
|
17228
17380
|
*/
|
17229
17381
|
(function(wysihtml5) {
|
17230
17382
|
var dom = wysihtml5.dom,
|
17383
|
+
domNode = dom.domNode,
|
17231
17384
|
browser = wysihtml5.browser,
|
17232
17385
|
/**
|
17233
17386
|
* Map keyCodes to query commands
|
@@ -17278,7 +17431,7 @@ wysihtml5.views.View = Base.extend(
|
|
17278
17431
|
return true;
|
17279
17432
|
}
|
17280
17433
|
try {
|
17281
|
-
var ev = new CustomEvent("wysihtml5:uneditable:delete");
|
17434
|
+
var ev = new CustomEvent("wysihtml5:uneditable:delete", {bubbles: true, cancelable: false});
|
17282
17435
|
before.node.dispatchEvent(ev);
|
17283
17436
|
} catch (err) {}
|
17284
17437
|
before.node.parentNode.removeChild(before.node);
|
@@ -17287,16 +17440,19 @@ wysihtml5.views.View = Base.extend(
|
|
17287
17440
|
return false;
|
17288
17441
|
};
|
17289
17442
|
|
17290
|
-
// Deletion with caret in the beginning of headings needs special attention
|
17291
|
-
//
|
17292
|
-
var
|
17443
|
+
// Deletion with caret in the beginning of headings and other block elvel elements needs special attention
|
17444
|
+
// Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
|
17445
|
+
var fixDeleteInTheBeginningOfBlock = function(composer) {
|
17293
17446
|
var selection = composer.selection,
|
17294
17447
|
prevNode = selection.getPreviousNode();
|
17295
17448
|
|
17296
17449
|
if (selection.caretIsFirstInSelection() &&
|
17297
17450
|
prevNode &&
|
17298
17451
|
prevNode.nodeType === 1 &&
|
17299
|
-
(/block/).test(composer.win.getComputedStyle(prevNode).display)
|
17452
|
+
(/block/).test(composer.win.getComputedStyle(prevNode).display) &&
|
17453
|
+
!domNode(prevNode).test({
|
17454
|
+
query: "ol, ul, table, tr, dl"
|
17455
|
+
})
|
17300
17456
|
) {
|
17301
17457
|
if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
|
17302
17458
|
// If heading is empty remove the heading node
|
@@ -17306,42 +17462,83 @@ wysihtml5.views.View = Base.extend(
|
|
17306
17462
|
if (prevNode.lastChild) {
|
17307
17463
|
var selNode = prevNode.lastChild,
|
17308
17464
|
selectedNode = selection.getSelectedNode(),
|
17309
|
-
commonAncestorNode =
|
17310
|
-
curNode =
|
17465
|
+
commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element);
|
17466
|
+
curNode = selectedNode.nodeType === 3 ? selectedNode : wysihtml5.dom.getParentElement(selectedNode, {
|
17311
17467
|
query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
|
17312
|
-
}, false, commonAncestorNode
|
17313
|
-
|
17314
|
-
|
17315
|
-
|
17316
|
-
|
17468
|
+
}, false, commonAncestorNode || composer.element);
|
17469
|
+
|
17470
|
+
if (curNode) {
|
17471
|
+
domNode(curNode).transferContentTo(prevNode, true);
|
17472
|
+
selection.setAfter(selNode);
|
17473
|
+
return true;
|
17474
|
+
}
|
17475
|
+
}
|
17476
|
+
}
|
17477
|
+
}
|
17478
|
+
return false;
|
17479
|
+
};
|
17480
|
+
|
17481
|
+
/* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
|
17482
|
+
/* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
|
17483
|
+
var fixDeleteInTheBeginningOfLi = function(composer) {
|
17484
|
+
if (wysihtml5.browser.hasLiDeletingProblem()) {
|
17485
|
+
var selection = composer.selection.getSelection(),
|
17486
|
+
aNode = selection.anchorNode,
|
17487
|
+
listNode, prevNode, firstNode,
|
17488
|
+
isInBeginnig = composer.selection.caretIsFirstInSelection();
|
17489
|
+
|
17490
|
+
// Fix caret at the beginnig of first textNode in LI
|
17491
|
+
if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
|
17492
|
+
aNode = aNode.parentNode;
|
17493
|
+
isInBeginnig = true;
|
17494
|
+
}
|
17495
|
+
|
17496
|
+
if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
|
17497
|
+
prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
17498
|
+
if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
|
17499
|
+
prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
17500
|
+
}
|
17501
|
+
if (prevNode) {
|
17502
|
+
firstNode = aNode.firstChild;
|
17503
|
+
domNode(aNode).transferContentTo(prevNode, true);
|
17504
|
+
if (firstNode) {
|
17505
|
+
composer.selection.setBefore(firstNode);
|
17506
|
+
} else if (prevNode) {
|
17507
|
+
if (prevNode.nodeType === 1) {
|
17508
|
+
if (prevNode.lastChild) {
|
17509
|
+
composer.selection.setAfter(prevNode.lastChild);
|
17510
|
+
} else {
|
17511
|
+
composer.selection.selectNode(prevNode);
|
17317
17512
|
}
|
17318
|
-
|
17319
|
-
|
17320
|
-
} else if (selectedNode.nodeType === 3) {
|
17321
|
-
prevNode.appendChild(selectedNode);
|
17322
|
-
selection.setAfter(selNode);
|
17323
|
-
return true;
|
17513
|
+
} else {
|
17514
|
+
composer.selection.setAfter(prevNode);
|
17324
17515
|
}
|
17516
|
+
}
|
17517
|
+
return true;
|
17325
17518
|
}
|
17326
17519
|
}
|
17327
17520
|
}
|
17328
17521
|
return false;
|
17329
|
-
}
|
17522
|
+
}
|
17330
17523
|
|
17331
17524
|
var handleDeleteKeyPress = function(event, composer) {
|
17332
17525
|
var selection = composer.selection,
|
17333
17526
|
element = composer.element;
|
17334
17527
|
|
17335
17528
|
if (selection.isCollapsed()) {
|
17336
|
-
if (
|
17529
|
+
if (handleUneditableDeletion(composer)) {
|
17337
17530
|
event.preventDefault();
|
17338
17531
|
return;
|
17339
17532
|
}
|
17340
|
-
if (
|
17533
|
+
if (fixDeleteInTheBeginningOfLi(composer)) {
|
17341
17534
|
event.preventDefault();
|
17342
17535
|
return;
|
17343
17536
|
}
|
17344
|
-
if (
|
17537
|
+
if (fixDeleteInTheBeginningOfBlock(composer)) {
|
17538
|
+
event.preventDefault();
|
17539
|
+
return;
|
17540
|
+
}
|
17541
|
+
if (fixLastBrDeletionInTable(composer)) {
|
17345
17542
|
event.preventDefault();
|
17346
17543
|
return;
|
17347
17544
|
}
|
@@ -17353,6 +17550,72 @@ wysihtml5.views.View = Base.extend(
|
|
17353
17550
|
}
|
17354
17551
|
};
|
17355
17552
|
|
17553
|
+
var handleEnterKeyPress = function(event, composer) {
|
17554
|
+
if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) {
|
17555
|
+
// Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
|
17556
|
+
|
17557
|
+
var breakNodes = "p, pre, div, blockquote",
|
17558
|
+
caretInfo, parent, txtNode;
|
17559
|
+
|
17560
|
+
if (composer.selection.isCollapsed()) {
|
17561
|
+
caretInfo = composer.selection.getNodesNearCaret();
|
17562
|
+
if (caretInfo) {
|
17563
|
+
|
17564
|
+
if (caretInfo.caretNode || caretInfo.nextNode) {
|
17565
|
+
parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
|
17566
|
+
if (parent === composer.element) {
|
17567
|
+
parent = undefined;
|
17568
|
+
}
|
17569
|
+
}
|
17570
|
+
|
17571
|
+
if (parent && caretInfo.caretNode) {
|
17572
|
+
if (domNode(caretInfo.caretNode).is.lineBreak()) {
|
17573
|
+
|
17574
|
+
if (composer.config.doubleLineBreakEscapesBlock) {
|
17575
|
+
// Double enter (enter on blank line) exits block element in useLineBreaks mode.
|
17576
|
+
event.preventDefault();
|
17577
|
+
caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
|
17578
|
+
|
17579
|
+
// Ensure surplous line breaks are not added to preceding element
|
17580
|
+
if (domNode(caretInfo.nextNode).is.lineBreak()) {
|
17581
|
+
caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
|
17582
|
+
}
|
17583
|
+
|
17584
|
+
var brNode = composer.doc.createElement('br');
|
17585
|
+
if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
|
17586
|
+
parent.parentNode.insertBefore(brNode, parent.nextSibling);
|
17587
|
+
} else {
|
17588
|
+
composer.selection.splitElementAtCaret(parent, brNode);
|
17589
|
+
}
|
17590
|
+
|
17591
|
+
// Ensure surplous blank lines are not added to preceding element
|
17592
|
+
if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
|
17593
|
+
// Replaces blank lines at the beginning of textnode
|
17594
|
+
caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
|
17595
|
+
}
|
17596
|
+
composer.selection.setBefore(brNode);
|
17597
|
+
}
|
17598
|
+
|
17599
|
+
} else if (caretInfo.caretNode.nodeType === 3 && wysihtml5.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
|
17600
|
+
|
17601
|
+
// This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
|
17602
|
+
// in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
|
17603
|
+
event.preventDefault();
|
17604
|
+
var br1 = composer.doc.createElement('br'),
|
17605
|
+
br2 = composer.doc.createElement('br'),
|
17606
|
+
f = composer.doc.createDocumentFragment();
|
17607
|
+
f.appendChild(br1);
|
17608
|
+
f.appendChild(br2);
|
17609
|
+
composer.selection.insertNode(f);
|
17610
|
+
composer.selection.setBefore(br2);
|
17611
|
+
|
17612
|
+
}
|
17613
|
+
}
|
17614
|
+
}
|
17615
|
+
}
|
17616
|
+
}
|
17617
|
+
};
|
17618
|
+
|
17356
17619
|
var handleTabKeyDown = function(composer, element, shiftKey) {
|
17357
17620
|
if (!composer.selection.isCollapsed()) {
|
17358
17621
|
composer.selection.deleteContents();
|
@@ -17448,26 +17711,6 @@ wysihtml5.views.View = Base.extend(
|
|
17448
17711
|
}
|
17449
17712
|
};
|
17450
17713
|
|
17451
|
-
// TODO: mouseover is not actually a foolproof and obvious place for this, must be changed as it modifies dom on random basis
|
17452
|
-
// Shows url in tooltip when hovering links or images
|
17453
|
-
var handleMouseOver = function(event) {
|
17454
|
-
var titlePrefixes = {
|
17455
|
-
IMG: "Image: ",
|
17456
|
-
A: "Link: "
|
17457
|
-
},
|
17458
|
-
target = event.target,
|
17459
|
-
nodeName = target.nodeName,
|
17460
|
-
title;
|
17461
|
-
|
17462
|
-
if (nodeName !== "A" && nodeName !== "IMG") {
|
17463
|
-
return;
|
17464
|
-
}
|
17465
|
-
if(!target.hasAttribute("title")){
|
17466
|
-
title = titlePrefixes[nodeName] + (target.getAttribute("href") || target.getAttribute("src"));
|
17467
|
-
target.setAttribute("title", title);
|
17468
|
-
}
|
17469
|
-
};
|
17470
|
-
|
17471
17714
|
var handleClick = function(event) {
|
17472
17715
|
if (this.config.classNames.uneditableContainer) {
|
17473
17716
|
// If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text)
|
@@ -17534,6 +17777,10 @@ wysihtml5.views.View = Base.extend(
|
|
17534
17777
|
handleTabKeyDown(this, this.element, event.shiftKey);
|
17535
17778
|
}
|
17536
17779
|
|
17780
|
+
if (keyCode === wysihtml5.ENTER_KEY) {
|
17781
|
+
handleEnterKeyPress(event, this);
|
17782
|
+
}
|
17783
|
+
|
17537
17784
|
};
|
17538
17785
|
|
17539
17786
|
var handleIframeFocus = function(event) {
|
@@ -17610,7 +17857,6 @@ wysihtml5.views.View = Base.extend(
|
|
17610
17857
|
addListeners(this.element, ["drop", "paste", "beforepaste"], handlePaste.bind(this), false);
|
17611
17858
|
this.element.addEventListener("copy", handleCopy.bind(this), false);
|
17612
17859
|
this.element.addEventListener("mousedown", handleMouseDown.bind(this), false);
|
17613
|
-
this.element.addEventListener("mouseover", handleMouseOver.bind(this), false);
|
17614
17860
|
this.element.addEventListener("click", handleClick.bind(this), false);
|
17615
17861
|
this.element.addEventListener("drop", handleDrop.bind(this), false);
|
17616
17862
|
this.element.addEventListener("keyup", handleKeyUp.bind(this), false);
|
@@ -17906,6 +18152,9 @@ wysihtml5.views.View = Base.extend(
|
|
17906
18152
|
parser: wysihtml5.dom.parse,
|
17907
18153
|
// By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
|
17908
18154
|
useLineBreaks: true,
|
18155
|
+
// Double enter (enter on blank line) exits block element in useLineBreaks mode.
|
18156
|
+
// It enables a way of escaping out of block elements and splitting block elements
|
18157
|
+
doubleLineBreakEscapesBlock: true,
|
17909
18158
|
// Array (or single string) of stylesheet urls to be loaded in the editor's iframe
|
17910
18159
|
stylesheets: [],
|
17911
18160
|
// Placeholder text to use, defaults to the placeholder attribute on the textarea element
|