wysihtml-rails 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f669d05b4737529b9d67aa2b90ec224ba385c19e
|
4
|
+
data.tar.gz: 90aaea2e8cdb31da89b4c0fc2d87d7cfaeeb58be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42679b7d2407ff704cb3ddff9b0d7ea393cacd7aafda17b4ea8d5aba0530ee602e871d16059168c02543d5162e5f93ba73b0e55c99a23730a5b5a2f94ba9e603
|
7
|
+
data.tar.gz: d73d3b367728f467d3018b74caaaac94555ad2a8dd27fedc7193a67bbd8b896a12008bc604f4c6d1034617ca1c7a890b9f5a24f54bb49fded489d84ae4841fbf
|
@@ -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);
|
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});
|
14192
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) {
|