wysihtml-rails 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|