wysihtml-rails 0.5.1 → 0.5.2
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.
- checksums.yaml +4 -4
- data/lib/wysihtml/rails/version.rb +1 -1
- data/vendor/assets/javascripts/wysihtml-toolbar.js +587 -253
- data/vendor/assets/javascripts/wysihtml.js +587 -253
- metadata +2 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license wysihtml v0.5.
|
2
|
+
* @license wysihtml v0.5.2
|
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.2",
|
14
14
|
|
15
15
|
// namespaces
|
16
16
|
commands: {},
|
@@ -76,19 +76,19 @@ var wysihtml5 = {
|
|
76
76
|
|
77
77
|
// element.textContent polyfill.
|
78
78
|
if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(win.Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(win.Element.prototype, "textContent").get) {
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
79
|
+
(function() {
|
80
|
+
var innerText = Object.getOwnPropertyDescriptor(win.Element.prototype, "innerText");
|
81
|
+
Object.defineProperty(win.Element.prototype, "textContent",
|
82
|
+
{
|
83
|
+
get: function() {
|
84
|
+
return innerText.get.call(this);
|
85
|
+
},
|
86
|
+
set: function(s) {
|
87
|
+
return innerText.set.call(this, s);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
);
|
91
|
+
})();
|
92
92
|
}
|
93
93
|
|
94
94
|
// isArray polyfill for ie8
|
@@ -133,20 +133,36 @@ var wysihtml5 = {
|
|
133
133
|
};
|
134
134
|
}
|
135
135
|
|
136
|
-
//
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
136
|
+
// closest and matches polyfill
|
137
|
+
// https://github.com/jonathantneal/closest
|
138
|
+
(function (ELEMENT) {
|
139
|
+
ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) {
|
140
|
+
var
|
141
|
+
element = this,
|
142
|
+
elements = (element.document || element.ownerDocument).querySelectorAll(selector),
|
143
|
+
index = 0;
|
144
|
+
|
145
|
+
while (elements[index] && elements[index] !== element) {
|
146
|
+
++index;
|
147
|
+
}
|
148
|
+
|
149
|
+
return elements[index] ? true : false;
|
148
150
|
};
|
149
|
-
|
151
|
+
|
152
|
+
ELEMENT.closest = ELEMENT.closest || function closest(selector) {
|
153
|
+
var element = this;
|
154
|
+
|
155
|
+
while (element) {
|
156
|
+
if (element.matches(selector)) {
|
157
|
+
break;
|
158
|
+
}
|
159
|
+
|
160
|
+
element = element.parentElement;
|
161
|
+
}
|
162
|
+
|
163
|
+
return element;
|
164
|
+
};
|
165
|
+
}(Element.prototype));
|
150
166
|
|
151
167
|
// Element.classList for ie8-9 (toggle all IE)
|
152
168
|
// source http://purl.eligrey.com/github/classList.js/blob/master/classList.js
|
@@ -4335,7 +4351,8 @@ wysihtml5.polyfills(window, document);
|
|
4335
4351
|
}
|
4336
4352
|
|
4337
4353
|
return api;
|
4338
|
-
}, this)
|
4354
|
+
}, this);
|
4355
|
+
;/**
|
4339
4356
|
* Text range module for Rangy.
|
4340
4357
|
* Text-based manipulation and searching of ranges and selections.
|
4341
4358
|
*
|
@@ -7869,6 +7886,11 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
7869
7886
|
return nodes;
|
7870
7887
|
}
|
7871
7888
|
|
7889
|
+
// Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
|
7890
|
+
function isBookmark(n) {
|
7891
|
+
return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
|
7892
|
+
}
|
7893
|
+
|
7872
7894
|
wysihtml5.dom.domNode = function(node) {
|
7873
7895
|
var defaultNodeTypes = [wysihtml5.ELEMENT_NODE, wysihtml5.TEXT_NODE];
|
7874
7896
|
|
@@ -7902,6 +7924,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
7902
7924
|
}
|
7903
7925
|
|
7904
7926
|
if (
|
7927
|
+
isBookmark(prevNode) || // is Rangy temporary boomark element (bypass)
|
7905
7928
|
(!wysihtml5.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
|
7906
7929
|
(options && options.ignoreBlankTexts && wysihtml5.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set
|
7907
7930
|
) {
|
@@ -7921,6 +7944,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
7921
7944
|
}
|
7922
7945
|
|
7923
7946
|
if (
|
7947
|
+
isBookmark(nextNode) || // is Rangy temporary boomark element (bypass)
|
7924
7948
|
(!wysihtml5.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
|
7925
7949
|
(options && options.ignoreBlankTexts && wysihtml5.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set
|
7926
7950
|
) {
|
@@ -8082,7 +8106,7 @@ wysihtml5.dom.copyAttributes = function(attributesToCopy) {
|
|
8082
8106
|
}
|
8083
8107
|
}
|
8084
8108
|
|
8085
|
-
if (properties.nodeName && node.nodeName !== properties.nodeName) {
|
8109
|
+
if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) {
|
8086
8110
|
return false;
|
8087
8111
|
}
|
8088
8112
|
|
@@ -12532,15 +12556,40 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12532
12556
|
* Select line where the caret is in
|
12533
12557
|
*/
|
12534
12558
|
selectLine: function() {
|
12559
|
+
var r = rangy.createRange();
|
12535
12560
|
if (wysihtml5.browser.supportsSelectionModify()) {
|
12536
12561
|
this._selectLine_W3C();
|
12537
|
-
} else if (
|
12538
|
-
|
12539
|
-
|
12540
|
-
// For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
|
12541
|
-
this._selectLineUniversal();
|
12562
|
+
} else if (r.nativeRange && r.nativeRange.getBoundingClientRect) {
|
12563
|
+
// For IE Edge as it ditched the old api and did not fully implement the new one (as expected)*/
|
12564
|
+
this._selectLineUniversal();
|
12542
12565
|
}
|
12543
12566
|
},
|
12567
|
+
|
12568
|
+
includeRangyRangeHelpers: function() {
|
12569
|
+
var s = this.getSelection(),
|
12570
|
+
r = s.getRangeAt(0),
|
12571
|
+
isHelperNode = function(node) {
|
12572
|
+
return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'));
|
12573
|
+
},
|
12574
|
+
getNodeLength = function (node) {
|
12575
|
+
if (node.nodeType === 1) {
|
12576
|
+
return node.childNodes && node.childNodes.length || 0;
|
12577
|
+
} else {
|
12578
|
+
return node.data && node.data.length || 0;
|
12579
|
+
}
|
12580
|
+
// body...
|
12581
|
+
},
|
12582
|
+
anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
|
12583
|
+
fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode;
|
12584
|
+
|
12585
|
+
if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) {
|
12586
|
+
r.setEndAfter(fnode.nextSibling);
|
12587
|
+
}
|
12588
|
+
if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) {
|
12589
|
+
r.setStartBefore(anode.previousSibling);
|
12590
|
+
}
|
12591
|
+
r.select();
|
12592
|
+
},
|
12544
12593
|
|
12545
12594
|
/**
|
12546
12595
|
* See https://developer.mozilla.org/en/DOM/Selection/modify
|
@@ -12559,6 +12608,8 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12559
12608
|
selection.focusOffset === initialBoundry[3]
|
12560
12609
|
) {
|
12561
12610
|
this._selectLineUniversal();
|
12611
|
+
} else {
|
12612
|
+
this.includeRangyRangeHelpers();
|
12562
12613
|
}
|
12563
12614
|
},
|
12564
12615
|
|
@@ -12610,19 +12661,45 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12610
12661
|
rect,
|
12611
12662
|
startRange, endRange, testRange,
|
12612
12663
|
count = 0,
|
12613
|
-
amount, testRect, found
|
12664
|
+
amount, testRect, found,
|
12665
|
+
that = this,
|
12666
|
+
isLineBreakingElement = function(el) {
|
12667
|
+
return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml5.lang.array(['BR', 'HR']).contains(el.nodeName));
|
12668
|
+
},
|
12669
|
+
prevNode = function(node) {
|
12670
|
+
var pnode = node;
|
12671
|
+
if (pnode) {
|
12672
|
+
while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) {
|
12673
|
+
pnode = pnode.previousSibling;
|
12674
|
+
}
|
12675
|
+
}
|
12676
|
+
return pnode;
|
12677
|
+
};
|
12614
12678
|
|
12615
12679
|
startRange = r.cloneRange();
|
12616
12680
|
endRange = r.cloneRange();
|
12617
12681
|
|
12618
12682
|
if (r.collapsed) {
|
12619
|
-
|
12620
|
-
|
12683
|
+
// Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary
|
12684
|
+
// TODO: figure out a shorter and more readable way
|
12685
|
+
if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) {
|
12686
|
+
r.moveEnd('character', 1);
|
12687
|
+
} else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) {
|
12688
|
+
r.moveEnd('character', 1);
|
12689
|
+
} else if (r.startOffset > 0 && ( r.startContainer.nodeType === 3 || (r.startContainer.nodeType === 1 && !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))))) {
|
12690
|
+
r.moveStart('character', -1);
|
12691
|
+
}
|
12621
12692
|
}
|
12622
|
-
|
12693
|
+
if (!r.collapsed) {
|
12694
|
+
r.insertNode(this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE));
|
12695
|
+
}
|
12696
|
+
|
12697
|
+
// Is probably just empty line as can not be expanded
|
12698
|
+
rect = r.nativeRange.getBoundingClientRect();
|
12623
12699
|
do {
|
12624
12700
|
amount = r.moveStart('character', -1);
|
12625
12701
|
testRect = r.nativeRange.getBoundingClientRect();
|
12702
|
+
|
12626
12703
|
if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) {
|
12627
12704
|
r.moveStart('character', 1);
|
12628
12705
|
found = true;
|
@@ -12638,61 +12715,24 @@ wysihtml5.quirks.ensureProperClearing = (function() {
|
|
12638
12715
|
testRect = r.nativeRange.getBoundingClientRect();
|
12639
12716
|
if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
|
12640
12717
|
r.moveEnd('character', -1);
|
12718
|
+
|
12719
|
+
// Fix a IE line end marked by linebreak element although caret is before it
|
12720
|
+
// If causes problems should be changed to be applied only to IE
|
12721
|
+
if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
|
12722
|
+
if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
|
12723
|
+
r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
|
12724
|
+
} else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
|
12725
|
+
r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
|
12726
|
+
}
|
12727
|
+
}
|
12728
|
+
|
12641
12729
|
found = true;
|
12642
12730
|
}
|
12643
12731
|
count++;
|
12644
12732
|
} while (amount !== 0 && !found && count < 2000);
|
12645
12733
|
|
12646
12734
|
r.select();
|
12647
|
-
|
12648
|
-
|
12649
|
-
_selectLine_MSIE: function() {
|
12650
|
-
var range = this.doc.selection && this.doc.selection.createRange ? this.doc.selection.createRange() : this.doc.createRange(),
|
12651
|
-
rangeTop = range.boundingTop,
|
12652
|
-
scrollWidth = this.doc.body.scrollWidth,
|
12653
|
-
rangeBottom,
|
12654
|
-
rangeEnd,
|
12655
|
-
measureNode,
|
12656
|
-
i,
|
12657
|
-
j;
|
12658
|
-
|
12659
|
-
window.r = range;
|
12660
|
-
|
12661
|
-
if (!range.moveToPoint) {
|
12662
|
-
return;
|
12663
|
-
}
|
12664
|
-
|
12665
|
-
if (rangeTop === 0) {
|
12666
|
-
// Don't know why, but when the selection ends at the end of a line
|
12667
|
-
// range.boundingTop is 0
|
12668
|
-
measureNode = this.doc.createElement("span");
|
12669
|
-
this.insertNode(measureNode);
|
12670
|
-
rangeTop = measureNode.offsetTop;
|
12671
|
-
measureNode.parentNode.removeChild(measureNode);
|
12672
|
-
}
|
12673
|
-
|
12674
|
-
rangeTop += 1;
|
12675
|
-
|
12676
|
-
for (i=-10; i<scrollWidth; i+=2) {
|
12677
|
-
try {
|
12678
|
-
range.moveToPoint(i, rangeTop);
|
12679
|
-
break;
|
12680
|
-
} catch(e1) {}
|
12681
|
-
}
|
12682
|
-
|
12683
|
-
// Investigate the following in order to handle multi line selections
|
12684
|
-
// rangeBottom = rangeTop + (rangeHeight ? (rangeHeight - 1) : 0);
|
12685
|
-
rangeBottom = rangeTop;
|
12686
|
-
rangeEnd = this.doc.selection.createRange();
|
12687
|
-
for (j=scrollWidth; j>=0; j--) {
|
12688
|
-
try {
|
12689
|
-
rangeEnd.moveToPoint(j, rangeBottom);
|
12690
|
-
break;
|
12691
|
-
} catch(e2) {}
|
12692
|
-
}
|
12693
|
-
|
12694
|
-
range.setEndPoint("EndToEnd", rangeEnd);
|
12695
|
-
range.select();
|
12735
|
+
this.includeRangyRangeHelpers();
|
12696
12736
|
},
|
12697
12737
|
|
12698
12738
|
getText: function() {
|
@@ -13993,18 +14033,56 @@ wysihtml5.Commands = Base.extend(
|
|
13993
14033
|
};
|
13994
14034
|
}
|
13995
14035
|
|
14036
|
+
function getRangeNode(node, offset) {
|
14037
|
+
if (node.nodeType === 3) {
|
14038
|
+
return node;
|
14039
|
+
} else {
|
14040
|
+
return node.childNodes[offset] || node;
|
14041
|
+
}
|
14042
|
+
}
|
14043
|
+
|
14044
|
+
// Returns if node is a line break
|
14045
|
+
function isBr(n) {
|
14046
|
+
return n && n.nodeType === 1 && n.nodeName === "BR";
|
14047
|
+
}
|
14048
|
+
|
14049
|
+
// Is block level element
|
14050
|
+
function isBlock(n, composer) {
|
14051
|
+
return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block";
|
14052
|
+
}
|
14053
|
+
|
14054
|
+
// Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
|
14055
|
+
function isBookmark(n) {
|
14056
|
+
return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
|
14057
|
+
}
|
14058
|
+
|
14059
|
+
// Is line breaking node
|
14060
|
+
function isLineBreaking(n, composer) {
|
14061
|
+
return isBr(n) || isBlock(n, composer);
|
14062
|
+
}
|
14063
|
+
|
13996
14064
|
// Removes empty block level elements
|
13997
|
-
function cleanup(composer) {
|
14065
|
+
function cleanup(composer, newBlockElements) {
|
14066
|
+
wysihtml5.dom.removeInvisibleSpaces(composer.element);
|
13998
14067
|
var container = composer.element,
|
13999
14068
|
allElements = container.querySelectorAll(BLOCK_ELEMENTS),
|
14000
|
-
|
14001
|
-
|
14069
|
+
noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '),
|
14070
|
+
uneditables = container.querySelectorAll(noEditQuery),
|
14071
|
+
elements = wysihtml5.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents
|
14072
|
+
nbIdx;
|
14002
14073
|
|
14003
14074
|
for (var i = elements.length; i--;) {
|
14004
14075
|
if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "") {
|
14076
|
+
// If cleanup removes some new block elements. remove them from newblocks array too
|
14077
|
+
nbIdx = wysihtml5.lang.array(newBlockElements).indexOf(elements[i]);
|
14078
|
+
if (nbIdx > -1) {
|
14079
|
+
newBlockElements.splice(nbIdx, 1);
|
14080
|
+
}
|
14005
14081
|
elements[i].parentNode.removeChild(elements[i]);
|
14006
14082
|
}
|
14007
14083
|
}
|
14084
|
+
|
14085
|
+
return newBlockElements;
|
14008
14086
|
}
|
14009
14087
|
|
14010
14088
|
function defaultNodeName(composer) {
|
@@ -14026,13 +14104,15 @@ wysihtml5.Commands = Base.extend(
|
|
14026
14104
|
return block;
|
14027
14105
|
}
|
14028
14106
|
|
14107
|
+
// Clone for splitting the inner inline element out of its parent inline elements context
|
14108
|
+
// For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return
|
14029
14109
|
function cloneOuterInlines(node, container) {
|
14030
14110
|
var n = node,
|
14031
14111
|
innerNode,
|
14032
14112
|
parentNode,
|
14033
14113
|
el = null,
|
14034
14114
|
el2;
|
14035
|
-
|
14115
|
+
|
14036
14116
|
while (n && container && n !== container) {
|
14037
14117
|
if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) {
|
14038
14118
|
parentNode = n;
|
@@ -14088,7 +14168,10 @@ wysihtml5.Commands = Base.extend(
|
|
14088
14168
|
// Unsets element properties by options
|
14089
14169
|
// If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes)
|
14090
14170
|
function removeOptionsFromElement(element, options, composer) {
|
14091
|
-
var style, classes
|
14171
|
+
var style, classes,
|
14172
|
+
prevNode = element.previousSibling,
|
14173
|
+
nextNode = element.nextSibling,
|
14174
|
+
unwrapped = false;
|
14092
14175
|
|
14093
14176
|
if (options.styleProperty) {
|
14094
14177
|
element.style[wysihtml5.browser.fixStyleKey(options.styleProperty)] = '';
|
@@ -14106,10 +14189,11 @@ wysihtml5.Commands = Base.extend(
|
|
14106
14189
|
element.removeAttribute('class');
|
14107
14190
|
}
|
14108
14191
|
|
14109
|
-
if (options.nodeName && element.nodeName === options.nodeName) {
|
14192
|
+
if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) {
|
14110
14193
|
style = element.getAttribute('style');
|
14111
14194
|
if (!style || style.trim() === '') {
|
14112
14195
|
dom.unwrap(element);
|
14196
|
+
unwrapped = true;
|
14113
14197
|
} else {
|
14114
14198
|
element = dom.renameElement(element, defaultNodeName(composer));
|
14115
14199
|
}
|
@@ -14119,60 +14203,79 @@ wysihtml5.Commands = Base.extend(
|
|
14119
14203
|
if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") {
|
14120
14204
|
element.removeAttribute('style');
|
14121
14205
|
}
|
14206
|
+
|
14207
|
+
if (unwrapped) {
|
14208
|
+
applySurroundingLineBreaks(prevNode, nextNode, composer);
|
14209
|
+
}
|
14122
14210
|
}
|
14123
14211
|
|
14124
14212
|
// Unwraps block level elements from inside content
|
14125
14213
|
// Useful as not all block level elements can contain other block-levels
|
14126
14214
|
function unwrapBlocksFromContent(element) {
|
14127
|
-
var
|
14215
|
+
var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents
|
14216
|
+
nextEl, prevEl;
|
14128
14217
|
|
14129
|
-
for (var i =
|
14130
|
-
|
14131
|
-
|
14132
|
-
|
14218
|
+
for (var i = blocks.length; i--;) {
|
14219
|
+
nextEl = wysihtml5.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
14220
|
+
prevEl = wysihtml5.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
14221
|
+
|
14222
|
+
if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
|
14223
|
+
if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
|
14224
|
+
blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
|
14133
14225
|
}
|
14134
14226
|
}
|
14135
|
-
|
14227
|
+
if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
|
14228
|
+
if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
|
14229
|
+
blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
|
14230
|
+
}
|
14231
|
+
}
|
14232
|
+
wysihtml5.dom.unwrap(blocks[i]);
|
14136
14233
|
}
|
14137
14234
|
}
|
14138
14235
|
|
14139
14236
|
// Fix ranges that visually cover whole block element to actually cover the block
|
14140
14237
|
function fixRangeCoverage(range, composer) {
|
14141
|
-
var node
|
14238
|
+
var node,
|
14239
|
+
start = range.startContainer,
|
14240
|
+
end = range.endContainer;
|
14142
14241
|
|
14143
|
-
|
14144
|
-
|
14145
|
-
|
14146
|
-
|
14147
|
-
|
14242
|
+
// If range has only one childNode and it is end to end the range, extend the range to contain the container element too
|
14243
|
+
// This ensures the wrapper node is modified and optios added to it
|
14244
|
+
if (start && start.nodeType === 1 && start === end) {
|
14245
|
+
if (start.firstChild === start.lastChild && range.endOffset === 1) {
|
14246
|
+
if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
|
14247
|
+
range.setStartBefore(start);
|
14248
|
+
range.setEndAfter(end);
|
14148
14249
|
}
|
14149
14250
|
}
|
14150
14251
|
return;
|
14151
14252
|
}
|
14152
14253
|
|
14153
|
-
|
14154
|
-
|
14155
|
-
|
14156
|
-
|
14254
|
+
// If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too
|
14255
|
+
if (start && start.nodeType === 1 && end.nodeType === 3) {
|
14256
|
+
if (start.firstChild === end && range.endOffset === end.data.length) {
|
14257
|
+
if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
|
14258
|
+
range.setEndAfter(start);
|
14157
14259
|
}
|
14158
14260
|
}
|
14159
14261
|
return;
|
14160
14262
|
}
|
14161
|
-
|
14162
|
-
|
14163
|
-
|
14164
|
-
|
14165
|
-
|
14263
|
+
|
14264
|
+
// If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too
|
14265
|
+
if (end && end.nodeType === 1 && start.nodeType === 3) {
|
14266
|
+
if (end.firstChild === start && range.startOffset === 0) {
|
14267
|
+
if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') {
|
14268
|
+
range.setStartBefore(end);
|
14166
14269
|
}
|
14167
14270
|
}
|
14168
14271
|
return;
|
14169
14272
|
}
|
14170
14273
|
|
14171
|
-
|
14172
|
-
if (
|
14173
|
-
if (range.
|
14174
|
-
node =
|
14175
|
-
if (node !== composer.element) {
|
14274
|
+
// If range covers a whole textnode and the textnode is the only child of node, extend range to node
|
14275
|
+
if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) {
|
14276
|
+
if (range.endOffset == end.data.length && range.startOffset === 0) {
|
14277
|
+
node = start.parentNode;
|
14278
|
+
if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') {
|
14176
14279
|
range.setStartBefore(node);
|
14177
14280
|
range.setEndAfter(node);
|
14178
14281
|
}
|
@@ -14180,108 +14283,285 @@ wysihtml5.Commands = Base.extend(
|
|
14180
14283
|
return;
|
14181
14284
|
}
|
14182
14285
|
}
|
14286
|
+
|
14287
|
+
// Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges
|
14288
|
+
// Some places do not allow block level elements inbetween (inside ul and outside li)
|
14289
|
+
// TODO: might need extending for other nodes besides li (maybe dd,dl,dt)
|
14290
|
+
function fixNotPermittedInsertionPoints(ranges) {
|
14291
|
+
var newRanges = [],
|
14292
|
+
lis, j, maxj, tmpRange, rangePos, closestLI;
|
14293
|
+
|
14294
|
+
for (var i = 0, maxi = ranges.length; i < maxi; i++) {
|
14295
|
+
|
14296
|
+
// Fixes range start and end positions if inside UL or OL element (outside of LI)
|
14297
|
+
if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) {
|
14298
|
+
ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0);
|
14299
|
+
}
|
14300
|
+
if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) {
|
14301
|
+
closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)];
|
14302
|
+
if (closestLI.childNodes) {
|
14303
|
+
ranges[i].setEnd(closestLI, closestLI.childNodes.length);
|
14304
|
+
}
|
14305
|
+
}
|
14183
14306
|
|
14184
|
-
|
14185
|
-
|
14186
|
-
|
14187
|
-
|
14188
|
-
|
14189
|
-
|
14307
|
+
// Get all LI eleemnts in selection (fully or partially covered)
|
14308
|
+
// And make sure ranges are either inside LI or outside UL/OL
|
14309
|
+
// Split and add new ranges as needed to cover same range content
|
14310
|
+
// TODO: Needs improvement to accept DL, DD, DT
|
14311
|
+
lis = ranges[i].getNodes([1], function(node) {
|
14312
|
+
return node.nodeName === "LI";
|
14313
|
+
});
|
14314
|
+
if (lis.length > 0) {
|
14315
|
+
|
14316
|
+
for (j = 0, maxj = lis.length; j < maxj; j++) {
|
14317
|
+
rangePos = ranges[i].compareNode(lis[j]);
|
14318
|
+
|
14319
|
+
// Fixes start of range that crosses LI border
|
14320
|
+
if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) {
|
14321
|
+
// Range starts before and ends inside the node
|
14322
|
+
|
14323
|
+
tmpRange = ranges[i].cloneRange();
|
14324
|
+
closestLI = wysihtml5.dom.domNode(lis[j]).prev({nodeTypes: [1]});
|
14325
|
+
|
14326
|
+
if (closestLI) {
|
14327
|
+
tmpRange.setEnd(closestLI, closestLI.childNodes.length);
|
14328
|
+
} else if (lis[j].closest('ul, ol')) {
|
14329
|
+
tmpRange.setEndBefore(lis[j].closest('ul, ol'));
|
14330
|
+
} else {
|
14331
|
+
tmpRange.setEndBefore(lis[j]);
|
14332
|
+
}
|
14333
|
+
newRanges.push(tmpRange);
|
14334
|
+
ranges[i].setStart(lis[j], 0);
|
14335
|
+
}
|
14336
|
+
|
14337
|
+
// Fixes end of range that crosses li border
|
14338
|
+
if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) {
|
14339
|
+
// Range starts inside the node and ends after node
|
14340
|
+
|
14341
|
+
tmpRange = ranges[i].cloneRange();
|
14342
|
+
tmpRange.setEnd(lis[j], lis[j].childNodes.length);
|
14343
|
+
newRanges.push(tmpRange);
|
14344
|
+
|
14345
|
+
// Find next LI in list and if present set range to it, else
|
14346
|
+
closestLI = wysihtml5.dom.domNode(lis[j]).next({nodeTypes: [1]});
|
14347
|
+
if (closestLI) {
|
14348
|
+
ranges[i].setStart(closestLI, 0);
|
14349
|
+
} else if (lis[j].closest('ul, ol')) {
|
14350
|
+
ranges[i].setStartAfter(lis[j].closest('ul, ol'));
|
14351
|
+
} else {
|
14352
|
+
ranges[i].setStartAfter(lis[j]);
|
14353
|
+
}
|
14354
|
+
}
|
14355
|
+
}
|
14356
|
+
newRanges.push(ranges[i]);
|
14357
|
+
} else {
|
14358
|
+
newRanges.push(ranges[i]);
|
14359
|
+
}
|
14190
14360
|
}
|
14191
|
-
|
14361
|
+
return newRanges;
|
14362
|
+
}
|
14363
|
+
|
14364
|
+
// Return options object with nodeName set if original did not have any
|
14365
|
+
// Node name is set to local or global default
|
14366
|
+
function getOptionsWithNodename(options, defaultName, composer) {
|
14367
|
+
var correctedOptions = (options) ? wysihtml5.lang.object(options).clone(true) : null;
|
14368
|
+
if (correctedOptions) {
|
14369
|
+
correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer);
|
14370
|
+
}
|
14371
|
+
return correctedOptions;
|
14372
|
+
}
|
14373
|
+
|
14374
|
+
// Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted
|
14375
|
+
// Also wraps empty clones of split parent tags around fragment to keep formatting
|
14376
|
+
// If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not)
|
14377
|
+
function injectFragmentToRange(fragment, range, composer, firstOuterBlock) {
|
14378
|
+
var rangeStartContainer = range.startContainer,
|
14379
|
+
firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true),
|
14380
|
+
outerInlines, first, last, prev, next;
|
14381
|
+
|
14382
|
+
if (firstOuterBlock) {
|
14383
|
+
// If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
|
14384
|
+
first = fragment.firstChild;
|
14385
|
+
last = fragment.lastChild;
|
14386
|
+
|
14387
|
+
composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
|
14192
14388
|
|
14389
|
+
next = wysihtml5.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true});
|
14390
|
+
prev = wysihtml5.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
14391
|
+
|
14392
|
+
if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) {
|
14393
|
+
first.parentNode.insertBefore(composer.doc.createElement('br'), first);
|
14394
|
+
}
|
14395
|
+
|
14396
|
+
if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) {
|
14397
|
+
next.parentNode.insertBefore(composer.doc.createElement('br'), next);
|
14398
|
+
}
|
14399
|
+
|
14400
|
+
} else {
|
14401
|
+
// Ensure node does not get inserted into an inline where it is not allowed
|
14402
|
+
outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
|
14403
|
+
if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
|
14404
|
+
if (fragment.childNodes.length === 1) {
|
14405
|
+
while(fragment.firstChild.firstChild) {
|
14406
|
+
outerInlines.innerNode.appendChild(fragment.firstChild.firstChild);
|
14407
|
+
}
|
14408
|
+
fragment.firstChild.appendChild(outerInlines.outerNode);
|
14409
|
+
}
|
14410
|
+
composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
|
14411
|
+
} else {
|
14412
|
+
// Otherwise just insert
|
14413
|
+
range.insertNode(fragment);
|
14414
|
+
}
|
14415
|
+
}
|
14416
|
+
}
|
14417
|
+
|
14418
|
+
// Removes all block formatting from range
|
14419
|
+
function clearRangeBlockFromating(range, closestBlockName, composer) {
|
14193
14420
|
var r = range.cloneRange(),
|
14421
|
+
prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling,
|
14422
|
+
nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling,
|
14423
|
+
content = r.extractContents(),
|
14424
|
+
fragment = composer.doc.createDocumentFragment(),
|
14425
|
+
children, blocks,
|
14426
|
+
first = true;
|
14427
|
+
|
14428
|
+
while(content.firstChild) {
|
14429
|
+
// Iterate over all selection content first level childNodes
|
14430
|
+
if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
|
14431
|
+
// If node is a block element
|
14432
|
+
// Split block formating and add new block to wrap caret
|
14433
|
+
|
14434
|
+
unwrapBlocksFromContent(content.firstChild);
|
14435
|
+
children = wysihtml5.dom.unwrap(content.firstChild);
|
14436
|
+
|
14437
|
+
// Add line break before if needed
|
14438
|
+
if (children.length > 0) {
|
14439
|
+
if (
|
14440
|
+
(fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) ||
|
14441
|
+
(!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer)))
|
14442
|
+
){
|
14443
|
+
fragment.appendChild(composer.doc.createElement('BR'));
|
14444
|
+
}
|
14445
|
+
}
|
14446
|
+
|
14447
|
+
for (var c = 0, cmax = children.length; c < cmax; c++) {
|
14448
|
+
fragment.appendChild(children[c]);
|
14449
|
+
}
|
14450
|
+
|
14451
|
+
// Add line break after if needed
|
14452
|
+
if (children.length > 0) {
|
14453
|
+
if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) {
|
14454
|
+
if (nextNode || fragment.lastChild !== content.lastChild) {
|
14455
|
+
fragment.appendChild(composer.doc.createElement('BR'));
|
14456
|
+
}
|
14457
|
+
}
|
14458
|
+
}
|
14459
|
+
|
14460
|
+
} else {
|
14461
|
+
fragment.appendChild(content.firstChild);
|
14462
|
+
}
|
14463
|
+
|
14464
|
+
first = false;
|
14465
|
+
}
|
14466
|
+
blocks = wysihtml5.lang.array(fragment.childNodes).get();
|
14467
|
+
injectFragmentToRange(fragment, r, composer);
|
14468
|
+
return blocks;
|
14469
|
+
}
|
14470
|
+
|
14471
|
+
// When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself)
|
14472
|
+
function removeSurroundingLineBreaks(prevNode, nextNode, composer) {
|
14473
|
+
var prevPrev = prevNode && wysihtml5.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
14474
|
+
if (isBr(nextNode)) {
|
14475
|
+
nextNode.parentNode.removeChild(nextNode);
|
14476
|
+
}
|
14477
|
+
if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) {
|
14478
|
+
prevNode.parentNode.removeChild(prevNode);
|
14479
|
+
}
|
14480
|
+
}
|
14481
|
+
|
14482
|
+
function applySurroundingLineBreaks(prevNode, nextNode, composer) {
|
14483
|
+
var prevPrev;
|
14484
|
+
|
14485
|
+
if (prevNode && isBookmark(prevNode)) {
|
14486
|
+
prevNode = prevNode.previousSibling;
|
14487
|
+
}
|
14488
|
+
if (nextNode && isBookmark(nextNode)) {
|
14489
|
+
nextNode = nextNode.nextSibling;
|
14490
|
+
}
|
14491
|
+
|
14492
|
+
prevPrev = prevNode && prevNode.previousSibling;
|
14493
|
+
|
14494
|
+
if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) {
|
14495
|
+
prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling);
|
14496
|
+
}
|
14497
|
+
|
14498
|
+
if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) {
|
14499
|
+
nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode);
|
14500
|
+
}
|
14501
|
+
}
|
14502
|
+
|
14503
|
+
// Wrap the range with a block level element
|
14504
|
+
// If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
|
14505
|
+
function wrapRangeWithElement(range, options, closestBlockName, composer) {
|
14506
|
+
var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
|
14507
|
+
r = range.cloneRange(),
|
14194
14508
|
rangeStartContainer = r.startContainer,
|
14509
|
+
prevNode = wysihtml5.dom.domNode(getRangeNode(r.startContainer, r.startOffset)).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
14510
|
+
nextNode = wysihtml5.dom.domNode(getRangeNode(r.endContainer, r.endOffset)).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
14195
14511
|
content = r.extractContents(),
|
14196
14512
|
fragment = composer.doc.createDocumentFragment(),
|
14197
|
-
similarOptions = defaultOptions ? correctOptionsForSimilarityCheck(defaultOptions) : null,
|
14198
14513
|
similarOuterBlock = similarOptions ? wysihtml5.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
|
14199
|
-
splitAllBlocks = !
|
14514
|
+
splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
|
14200
14515
|
firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
|
14201
14516
|
wrapper, blocks, children;
|
14202
14517
|
|
14203
|
-
if (options && options.nodeName
|
14518
|
+
if (options && options.nodeName === "BLOCKQUOTE") {
|
14519
|
+
|
14520
|
+
// If blockquote is to be inserted no quessing just add it as outermost block on line or selection
|
14204
14521
|
var tmpEl = applyOptionsToElement(null, options, composer);
|
14205
14522
|
tmpEl.appendChild(content);
|
14206
14523
|
fragment.appendChild(tmpEl);
|
14207
14524
|
blocks = [tmpEl];
|
14525
|
+
|
14208
14526
|
} else {
|
14209
14527
|
|
14210
14528
|
if (!content.firstChild) {
|
14529
|
+
// IF selection is caret (can happen if line is empty) add format around tag
|
14211
14530
|
fragment.appendChild(applyOptionsToElement(null, options, composer));
|
14212
14531
|
} else {
|
14213
14532
|
|
14214
14533
|
while(content.firstChild) {
|
14534
|
+
// Iterate over all selection content first level childNodes
|
14215
14535
|
|
14216
14536
|
if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
|
14217
14537
|
|
14218
|
-
|
14219
|
-
|
14220
|
-
|
14221
|
-
|
14222
|
-
unwrapBlocksFromContent(content.firstChild);
|
14223
|
-
}
|
14224
|
-
fragment.appendChild(content.firstChild);
|
14225
|
-
|
14226
|
-
} else {
|
14227
|
-
// Split block formating and add new block to wrap caret
|
14538
|
+
// If node is a block element
|
14539
|
+
// Escape(split) block formatting at caret
|
14540
|
+
applyOptionsToElement(content.firstChild, options, composer);
|
14541
|
+
if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
|
14228
14542
|
unwrapBlocksFromContent(content.firstChild);
|
14229
|
-
children = wysihtml5.dom.unwrap(content.firstChild);
|
14230
|
-
for (var c = 0, cmax = children.length; c < cmax; c++) {
|
14231
|
-
fragment.appendChild(children[c]);
|
14232
|
-
}
|
14233
|
-
|
14234
|
-
if (fragment.childNodes.length > 0) {
|
14235
|
-
fragment.appendChild(composer.doc.createElement('BR'));
|
14236
|
-
}
|
14237
14543
|
}
|
14544
|
+
fragment.appendChild(content.firstChild);
|
14545
|
+
|
14238
14546
|
} else {
|
14239
|
-
|
14240
|
-
if (options) {
|
14241
|
-
// Wrap subsequent non-block nodes inside new block element
|
14242
|
-
wrapper = applyOptionsToElement(null, defaultOptions, composer);
|
14243
|
-
while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
|
14244
|
-
if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
|
14245
|
-
unwrapBlocksFromContent(content.firstChild);
|
14246
|
-
}
|
14247
|
-
wrapper.appendChild(content.firstChild);
|
14248
|
-
}
|
14249
|
-
fragment.appendChild(wrapper);
|
14250
14547
|
|
14251
|
-
|
14252
|
-
|
14253
|
-
|
14548
|
+
// Wrap subsequent non-block nodes inside new block element
|
14549
|
+
wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer);
|
14550
|
+
while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
|
14551
|
+
if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
|
14254
14552
|
unwrapBlocksFromContent(content.firstChild);
|
14255
14553
|
}
|
14256
|
-
|
14554
|
+
wrapper.appendChild(content.firstChild);
|
14257
14555
|
}
|
14258
|
-
|
14556
|
+
fragment.appendChild(wrapper);
|
14259
14557
|
}
|
14260
14558
|
}
|
14261
14559
|
}
|
14262
14560
|
|
14263
14561
|
blocks = wysihtml5.lang.array(fragment.childNodes).get();
|
14264
14562
|
}
|
14265
|
-
|
14266
|
-
|
14267
|
-
composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
|
14268
|
-
} else {
|
14269
|
-
// Ensure node does not get inserted into an inline where it is not allowed
|
14270
|
-
var outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
|
14271
|
-
if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
|
14272
|
-
if (fragment.childNodes.length === 1) {
|
14273
|
-
while(fragment.firstChild.firstChild) {
|
14274
|
-
outerInlines.innerNode.appendChild(fragment.firstChild.firstChild);
|
14275
|
-
}
|
14276
|
-
fragment.firstChild.appendChild(outerInlines.outerNode);
|
14277
|
-
}
|
14278
|
-
composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
|
14279
|
-
} else {
|
14280
|
-
// Otherwise just insert
|
14281
|
-
r.insertNode(fragment);
|
14282
|
-
}
|
14283
|
-
}
|
14284
|
-
|
14563
|
+
injectFragmentToRange(fragment, r, composer, firstOuterBlock);
|
14564
|
+
removeSurroundingLineBreaks(prevNode, nextNode, composer);
|
14285
14565
|
return blocks;
|
14286
14566
|
}
|
14287
14567
|
|
@@ -14293,101 +14573,154 @@ wysihtml5.Commands = Base.extend(
|
|
14293
14573
|
|
14294
14574
|
return (parentNode) ? parentNode.nodeName : null;
|
14295
14575
|
}
|
14576
|
+
|
14577
|
+
// Expands caret to cover the closest block that:
|
14578
|
+
// * cannot contain other block level elements (h1-6,p, etc)
|
14579
|
+
// * Has the same nodeName that is to be inserted
|
14580
|
+
// * has insertingNodeName
|
14581
|
+
// * is DIV if insertingNodeName is not present
|
14582
|
+
//
|
14583
|
+
// If nothing found selects the current line
|
14584
|
+
function expandCaretToBlock(composer, insertingNodeName) {
|
14585
|
+
var parent = wysihtml5.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
|
14586
|
+
query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'),
|
14587
|
+
}, null, composer.element),
|
14588
|
+
range;
|
14589
|
+
|
14590
|
+
if (parent) {
|
14591
|
+
range = composer.selection.createRange();
|
14592
|
+
range.selectNode(parent);
|
14593
|
+
composer.selection.setSelection(range);
|
14594
|
+
} else if (!composer.isEmpty()) {
|
14595
|
+
composer.selection.selectLine();
|
14596
|
+
}
|
14597
|
+
}
|
14598
|
+
|
14599
|
+
// Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element
|
14600
|
+
// TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
|
14601
|
+
function selectElements(newBlockElements, composer) {
|
14602
|
+
var range = composer.selection.createRange(),
|
14603
|
+
lastEl = newBlockElements[newBlockElements.length - 1],
|
14604
|
+
lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
|
14605
|
+
|
14606
|
+
range.setStart(newBlockElements[0], 0);
|
14607
|
+
range.setEnd(lastEl, lastOffset);
|
14608
|
+
range.select();
|
14609
|
+
}
|
14610
|
+
|
14611
|
+
// Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each
|
14612
|
+
// Return created/modified block level elements
|
14613
|
+
// Method can be either "apply" or "remove"
|
14614
|
+
function formatSelection(method, composer, options) {
|
14615
|
+
var ranges = composer.selection.getOwnRanges(),
|
14616
|
+
newBlockElements = [],
|
14617
|
+
closestBlockName;
|
14618
|
+
|
14619
|
+
// Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th)
|
14620
|
+
ranges = fixNotPermittedInsertionPoints(ranges);
|
14621
|
+
|
14622
|
+
for (var i = ranges.length; i--;) {
|
14623
|
+
fixRangeCoverage(ranges[i], composer);
|
14624
|
+
closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer);
|
14625
|
+
if (method === "remove") {
|
14626
|
+
newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer));
|
14627
|
+
} else {
|
14628
|
+
newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer));
|
14629
|
+
}
|
14630
|
+
}
|
14631
|
+
return newBlockElements;
|
14632
|
+
}
|
14633
|
+
|
14634
|
+
// If properties is passed as a string, look for tag with that tagName/query
|
14635
|
+
function parseOptions(options) {
|
14636
|
+
if (typeof options === "string") {
|
14637
|
+
options = {
|
14638
|
+
nodeName: options.toUpperCase()
|
14639
|
+
};
|
14640
|
+
}
|
14641
|
+
return options;
|
14642
|
+
}
|
14296
14643
|
|
14297
14644
|
wysihtml5.commands.formatBlock = {
|
14298
14645
|
exec: function(composer, command, options) {
|
14646
|
+
options = parseOptions(options);
|
14299
14647
|
var newBlockElements = [],
|
14300
|
-
|
14301
|
-
|
14302
|
-
// If properties is passed as a string, look for tag with that tagName/query
|
14303
|
-
if (typeof options === "string") {
|
14304
|
-
options = {
|
14305
|
-
nodeName: options.toUpperCase()
|
14306
|
-
};
|
14307
|
-
}
|
14648
|
+
ranges, range, bookmark, state, closestBlockName;
|
14308
14649
|
|
14309
|
-
//
|
14650
|
+
// Find if current format state is active if options.toggle is set as true
|
14651
|
+
// In toggle case active state elemets are formatted instead of working directly on selection
|
14310
14652
|
if (options && options.toggle) {
|
14311
14653
|
state = this.state(composer, command, options);
|
14312
|
-
if (state) {
|
14313
|
-
bookmark = rangy.saveSelection(composer.win);
|
14314
|
-
for (var j = 0, jmax = state.length; j < jmax; j++) {
|
14315
|
-
removeOptionsFromElement(state[j], options, composer);
|
14316
|
-
}
|
14317
|
-
}
|
14318
14654
|
}
|
14655
|
+
if (state) {
|
14656
|
+
// Remove format from state nodes if toggle set and state on and selection is collapsed
|
14657
|
+
bookmark = rangy.saveSelection(composer.win);
|
14658
|
+
for (var j = 0, jmax = state.length; j < jmax; j++) {
|
14659
|
+
removeOptionsFromElement(state[j], options, composer);
|
14660
|
+
}
|
14319
14661
|
|
14320
|
-
|
14321
|
-
|
14322
|
-
|
14662
|
+
} else {
|
14663
|
+
// If selection is caret expand it to cover nearest suitable block element or row if none found
|
14323
14664
|
if (composer.selection.isCollapsed()) {
|
14324
|
-
|
14325
|
-
|
14326
|
-
}, null, composer.element);
|
14327
|
-
if (parent) {
|
14328
|
-
bookmark = rangy.saveSelection(composer.win);
|
14329
|
-
range = composer.selection.createRange();
|
14330
|
-
range.selectNode(parent);
|
14331
|
-
composer.selection.setSelection(range);
|
14332
|
-
} else if (!composer.isEmpty()) {
|
14333
|
-
bookmark = rangy.saveSelection(composer.win);
|
14334
|
-
composer.selection.selectLine();
|
14335
|
-
}
|
14665
|
+
bookmark = rangy.saveSelection(composer.win);
|
14666
|
+
expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
|
14336
14667
|
}
|
14337
|
-
|
14338
|
-
|
14339
|
-
|
14340
|
-
|
14341
|
-
newBlockElements =
|
14668
|
+
if (options) {
|
14669
|
+
newBlockElements = formatSelection("apply", composer, options);
|
14670
|
+
} else {
|
14671
|
+
// Options == null means block formatting should be removed from selection
|
14672
|
+
newBlockElements = formatSelection("remove", composer);
|
14342
14673
|
}
|
14343
|
-
|
14674
|
+
|
14344
14675
|
}
|
14345
14676
|
|
14346
14677
|
// Remove empty block elements that may be left behind
|
14347
|
-
|
14348
|
-
|
14349
|
-
for (var e = newBlockElements.length; e--;) {
|
14350
|
-
if (!newBlockElements[e].parentNode) {
|
14351
|
-
newBlockElements.splice(e, 1);
|
14352
|
-
}
|
14353
|
-
}
|
14678
|
+
// Also remove them from new blocks list
|
14679
|
+
newBlockElements = cleanup(composer, newBlockElements);
|
14354
14680
|
|
14355
|
-
// Restore
|
14681
|
+
// Restore selection
|
14356
14682
|
if (bookmark) {
|
14357
|
-
wysihtml5.dom.removeInvisibleSpaces(composer.element);
|
14358
14683
|
rangy.restoreSelection(bookmark);
|
14359
14684
|
} else {
|
14360
|
-
|
14361
|
-
// Set selection to beging inside first created block element (beginning of it) and end inside (and after content) of last block element
|
14362
|
-
// TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
|
14363
|
-
range = composer.selection.createRange();
|
14364
|
-
range.setStart(newBlockElements[0], 0);
|
14365
|
-
var lastEl = newBlockElements[newBlockElements.length - 1],
|
14366
|
-
lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
|
14367
|
-
range.setEnd(lastEl, lastOffset);
|
14368
|
-
range.select();
|
14685
|
+
selectElements(newBlockElements, composer);
|
14369
14686
|
}
|
14370
14687
|
},
|
14371
|
-
|
14372
|
-
//
|
14373
|
-
|
14688
|
+
|
14689
|
+
// Removes all block formatting from selection
|
14690
|
+
remove: function(composer, command, options) {
|
14691
|
+
options = parseOptions(options);
|
14692
|
+
var newBlockElements, bookmark;
|
14374
14693
|
|
14375
|
-
// If
|
14376
|
-
if (
|
14377
|
-
|
14378
|
-
|
14379
|
-
};
|
14694
|
+
// If selection is caret expand it to cover nearest suitable block element or row if none found
|
14695
|
+
if (composer.selection.isCollapsed()) {
|
14696
|
+
bookmark = rangy.saveSelection(composer.win);
|
14697
|
+
expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
|
14380
14698
|
}
|
14699
|
+
|
14700
|
+
newBlockElements = formatSelection("remove", composer);
|
14701
|
+
newBlockElements = cleanup(composer, newBlockElements);
|
14702
|
+
|
14703
|
+
// Restore selection
|
14704
|
+
if (bookmark) {
|
14705
|
+
rangy.restoreSelection(bookmark);
|
14706
|
+
} else {
|
14707
|
+
selectElements(newBlockElements, composer);
|
14708
|
+
}
|
14709
|
+
},
|
14710
|
+
|
14711
|
+
// If options as null is passed returns status describing all block level elements
|
14712
|
+
state: function(composer, command, options) {
|
14713
|
+
options = parseOptions(options);
|
14381
14714
|
|
14382
14715
|
var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
|
14383
|
-
return wysihtml5.dom.domNode(element).test(
|
14716
|
+
return wysihtml5.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS });
|
14384
14717
|
}).bind(this)),
|
14385
14718
|
parentNodes = composer.selection.getSelectedOwnNodes(),
|
14386
14719
|
parent;
|
14387
14720
|
|
14388
14721
|
// Finds matching elements that are parents of selection and adds to nodes list
|
14389
14722
|
for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
|
14390
|
-
parent = dom.getParentElement(parentNodes[i],
|
14723
|
+
parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element);
|
14391
14724
|
if (parent && nodes.indexOf(parent) === -1) {
|
14392
14725
|
nodes.push(parent);
|
14393
14726
|
}
|
@@ -14597,6 +14930,9 @@ wysihtml5.Commands = Base.extend(
|
|
14597
14930
|
if (options.toggle !== false && element.classList.contains(options.className)) {
|
14598
14931
|
element.classList.remove(options.className);
|
14599
14932
|
} else {
|
14933
|
+
if (options.classRegExp) {
|
14934
|
+
element.className = element.className.replace(options.classRegExp, '');
|
14935
|
+
}
|
14600
14936
|
element.classList.add(options.className);
|
14601
14937
|
}
|
14602
14938
|
if (hasNoClass(element)) {
|
@@ -16247,7 +16583,7 @@ wysihtml5.views.View = Base.extend(
|
|
16247
16583
|
|
16248
16584
|
cleanUp: function(rules) {
|
16249
16585
|
var bookmark;
|
16250
|
-
if (this.selection) {
|
16586
|
+
if (this.selection && this.selection.isInThisEditable()) {
|
16251
16587
|
bookmark = rangy.saveSelection(this.win);
|
16252
16588
|
}
|
16253
16589
|
this.parent.parse(this.element, undefined, rules);
|
@@ -16419,6 +16755,8 @@ wysihtml5.views.View = Base.extend(
|
|
16419
16755
|
]).from(this.textarea.element).to(this.element);
|
16420
16756
|
}
|
16421
16757
|
|
16758
|
+
this._initAutoLinking();
|
16759
|
+
|
16422
16760
|
dom.addClass(this.element, this.config.classNames.composer);
|
16423
16761
|
//
|
16424
16762
|
// Make the editor look like the original textarea, by syncing styles
|
@@ -16451,7 +16789,6 @@ wysihtml5.views.View = Base.extend(
|
|
16451
16789
|
// Make sure that the browser avoids using inline styles whenever possible
|
16452
16790
|
this.commands.exec("styleWithCSS", false);
|
16453
16791
|
|
16454
|
-
this._initAutoLinking();
|
16455
16792
|
this._initObjectResizing();
|
16456
16793
|
this._initUndoManager();
|
16457
16794
|
this._initLineBreaking();
|
@@ -16485,10 +16822,7 @@ wysihtml5.views.View = Base.extend(
|
|
16485
16822
|
supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
|
16486
16823
|
|
16487
16824
|
if (supportsDisablingOfAutoLinking) {
|
16488
|
-
// I have no idea why IE edge deletes element content here when calling the command,
|
16489
|
-
var tmpHTML = this.element.innerHTML;
|
16490
16825
|
this.commands.exec("AutoUrlDetect", false, false);
|
16491
|
-
this.element.innerHTML = tmpHTML;
|
16492
16826
|
}
|
16493
16827
|
|
16494
16828
|
if (!this.config.autoLink) {
|