scrivito_editors 1.2.0 → 1.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/scrivito_editors/medium_editor.js +3 -1
- data/app/assets/stylesheets/scrivito_editors/stringlist_editor.css +4 -3
- data/vendor/assets/javascripts/medium-editor.js +585 -109
- data/vendor/assets/stylesheets/medium-editor-themes/flat.css +2 -2
- data/vendor/assets/stylesheets/medium-editor.css +6 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e6fb4fb773e3fffc14b9b3b7df14336507aace3
|
4
|
+
data.tar.gz: a315c79989d708d0e9b3f936eb56044ba82319c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 705a8a34d6c744073fa6ce5f20ea948a737bc1414170b61f827471130e1bdc617248c15c62370303f006fdae5f3d7f297c8aa26f495b74f78563d62119261fad
|
7
|
+
data.tar.gz: a7386bdc6a991955e484b1f996b41d1e81459efa3a07fef743606989cc40fcce59557a117edd6c9205525a8c03144e2682c7378110ab95a5edc4da33c6ec7499
|
@@ -66,7 +66,8 @@
|
|
66
66
|
return {
|
67
67
|
anchorPreview: false,
|
68
68
|
extensions: {
|
69
|
-
scrivito_anchor: new ScrivitoAnchor
|
69
|
+
scrivito_anchor: new ScrivitoAnchor,
|
70
|
+
imageDragging: {}
|
70
71
|
},
|
71
72
|
placeholder: false,
|
72
73
|
toolbar: {
|
@@ -99,6 +100,7 @@
|
|
99
100
|
activate: function(element) {
|
100
101
|
return activate(element);
|
101
102
|
},
|
103
|
+
default_options: defaultOptions,
|
102
104
|
options: function() {
|
103
105
|
return {};
|
104
106
|
}
|
@@ -4,7 +4,8 @@
|
|
4
4
|
*/
|
5
5
|
|
6
6
|
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete { padding-right: 10px; }
|
7
|
-
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i
|
7
|
+
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i,
|
8
|
+
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i:before {
|
8
9
|
-moz-osx-font-smoothing: grayscale;
|
9
10
|
-webkit-font-smoothing: antialiased;
|
10
11
|
color: #666;
|
@@ -17,9 +18,9 @@
|
|
17
18
|
vertical-align: middle;
|
18
19
|
}
|
19
20
|
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i { background: none; }
|
20
|
-
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i:
|
21
|
+
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-delete i:before { content: "\F03c"; }
|
21
22
|
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-tag.active+.tag-editor-delete,
|
22
|
-
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-tag.active+.tag-editor-delete i:
|
23
|
+
[data-scrivito-field-type=stringlist]+.tag-editor .tag-editor-tag.active+.tag-editor-delete i:before {
|
23
24
|
content: "";
|
24
25
|
cursor: text;
|
25
26
|
}
|
@@ -445,6 +445,11 @@ MediumEditor.extensions = {};
|
|
445
445
|
// by rg89
|
446
446
|
isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
|
447
447
|
|
448
|
+
isEdge: (/Edge\/\d+/).exec(navigator.userAgent) !== null,
|
449
|
+
|
450
|
+
// if firefox
|
451
|
+
isFF: (navigator.userAgent.toLowerCase().indexOf('firefox') > -1),
|
452
|
+
|
448
453
|
// http://stackoverflow.com/a/11752084/569101
|
449
454
|
isMac: (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0),
|
450
455
|
|
@@ -551,7 +556,7 @@ MediumEditor.extensions = {};
|
|
551
556
|
* not affected in any way.
|
552
557
|
*/
|
553
558
|
findOrCreateMatchingTextNodes: function (document, element, match) {
|
554
|
-
var treeWalker = document.createTreeWalker(element, NodeFilter.
|
559
|
+
var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ALL, null, false),
|
555
560
|
matchedNodes = [],
|
556
561
|
currentTextIndex = 0,
|
557
562
|
startReached = false,
|
@@ -559,30 +564,41 @@ MediumEditor.extensions = {};
|
|
559
564
|
newNode = null;
|
560
565
|
|
561
566
|
while ((currentNode = treeWalker.nextNode()) !== null) {
|
562
|
-
if (
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
567
|
+
if (currentNode.nodeType > 3) {
|
568
|
+
continue;
|
569
|
+
} else if (currentNode.nodeType === 3) {
|
570
|
+
if (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) {
|
571
|
+
startReached = true;
|
572
|
+
newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex);
|
573
|
+
}
|
574
|
+
if (startReached) {
|
575
|
+
Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex);
|
576
|
+
}
|
577
|
+
if (startReached && currentTextIndex === match.end) {
|
578
|
+
break; // Found the node(s) corresponding to the link. Break out and move on to the next.
|
579
|
+
} else if (startReached && currentTextIndex > (match.end + 1)) {
|
580
|
+
throw new Error('PerformLinking overshot the target!'); // should never happen...
|
581
|
+
}
|
574
582
|
|
575
|
-
|
576
|
-
|
577
|
-
|
583
|
+
if (startReached) {
|
584
|
+
matchedNodes.push(newNode || currentNode);
|
585
|
+
}
|
578
586
|
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
587
|
+
currentTextIndex += currentNode.nodeValue.length;
|
588
|
+
if (newNode !== null) {
|
589
|
+
currentTextIndex += newNode.nodeValue.length;
|
590
|
+
// Skip the newNode as we'll already have pushed it to the matches
|
591
|
+
treeWalker.nextNode();
|
592
|
+
}
|
593
|
+
newNode = null;
|
594
|
+
} else if (currentNode.tagName.toLowerCase() === 'img') {
|
595
|
+
if (!startReached && (match.start <= currentTextIndex)) {
|
596
|
+
startReached = true;
|
597
|
+
}
|
598
|
+
if (startReached) {
|
599
|
+
matchedNodes.push(currentNode);
|
600
|
+
}
|
584
601
|
}
|
585
|
-
newNode = null;
|
586
602
|
}
|
587
603
|
return matchedNodes;
|
588
604
|
},
|
@@ -650,6 +666,10 @@ MediumEditor.extensions = {};
|
|
650
666
|
* <blockquote> container, they are the elements returned.
|
651
667
|
*/
|
652
668
|
splitByBlockElements: function (element) {
|
669
|
+
if (element.nodeType !== 3 && element.nodeType !== 1) {
|
670
|
+
return [];
|
671
|
+
}
|
672
|
+
|
653
673
|
var toRet = [],
|
654
674
|
blockElementQuery = MediumEditor.util.blockContainerElementNames.join(',');
|
655
675
|
|
@@ -661,7 +681,7 @@ MediumEditor.extensions = {};
|
|
661
681
|
var child = element.childNodes[i];
|
662
682
|
if (child.nodeType === 3) {
|
663
683
|
toRet.push(child);
|
664
|
-
} else {
|
684
|
+
} else if (child.nodeType === 1) {
|
665
685
|
var blockElements = child.querySelectorAll(blockElementQuery);
|
666
686
|
if (blockElements.length === 0) {
|
667
687
|
toRet.push(child);
|
@@ -703,6 +723,22 @@ MediumEditor.extensions = {};
|
|
703
723
|
return nextNode;
|
704
724
|
},
|
705
725
|
|
726
|
+
// Find an element's previous sibling within a medium-editor element
|
727
|
+
// If one doesn't exist, find the closest ancestor's previous sibling
|
728
|
+
findPreviousSibling: function (node) {
|
729
|
+
if (!node || Util.isMediumEditorElement(node)) {
|
730
|
+
return false;
|
731
|
+
}
|
732
|
+
|
733
|
+
var previousSibling = node.previousSibling;
|
734
|
+
while (!previousSibling && !Util.isMediumEditorElement(node.parentNode)) {
|
735
|
+
node = node.parentNode;
|
736
|
+
previousSibling = node.previousSibling;
|
737
|
+
}
|
738
|
+
|
739
|
+
return previousSibling;
|
740
|
+
},
|
741
|
+
|
706
742
|
isDescendant: function isDescendant(parent, child, checkEquality) {
|
707
743
|
if (!parent || !child) {
|
708
744
|
return false;
|
@@ -806,11 +842,20 @@ MediumEditor.extensions = {};
|
|
806
842
|
|
807
843
|
// http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
808
844
|
insertHTMLCommand: function (doc, html) {
|
809
|
-
var selection, range, el, fragment, node, lastNode, toReplace
|
810
|
-
|
811
|
-
|
845
|
+
var selection, range, el, fragment, node, lastNode, toReplace,
|
846
|
+
res = false,
|
847
|
+
ecArgs = ['insertHTML', false, html];
|
848
|
+
|
849
|
+
/* Edge's implementation of insertHTML is just buggy right now:
|
850
|
+
* - Doesn't allow leading white space at the beginning of an element
|
851
|
+
* - Found a case when a <font size="2"> tag was inserted when calling alignCenter inside a blockquote
|
852
|
+
*
|
853
|
+
* There are likely other bugs, these are just the ones we found so far.
|
854
|
+
* For now, let's just use the same fallback we did for IE
|
855
|
+
*/
|
856
|
+
if (!MediumEditor.util.isEdge && doc.queryCommandSupported('insertHTML')) {
|
812
857
|
try {
|
813
|
-
return doc.execCommand(
|
858
|
+
return doc.execCommand.apply(doc, ecArgs);
|
814
859
|
} catch (ignore) {}
|
815
860
|
}
|
816
861
|
|
@@ -855,17 +900,26 @@ MediumEditor.extensions = {};
|
|
855
900
|
selection.removeAllRanges();
|
856
901
|
selection.addRange(range);
|
857
902
|
}
|
903
|
+
res = true;
|
858
904
|
}
|
905
|
+
|
906
|
+
// https://github.com/yabwe/medium-editor/issues/992
|
907
|
+
// If we're monitoring calls to execCommand, notify listeners as if a real call had happened
|
908
|
+
if (doc.execCommand.callListeners) {
|
909
|
+
doc.execCommand.callListeners(ecArgs, res);
|
910
|
+
}
|
911
|
+
return res;
|
859
912
|
},
|
860
913
|
|
861
914
|
execFormatBlock: function (doc, tagName) {
|
862
915
|
// Get the top level block element that contains the selection
|
863
|
-
var blockContainer = Util.getTopBlockContainer(MediumEditor.selection.getSelectionStart(doc))
|
916
|
+
var blockContainer = Util.getTopBlockContainer(MediumEditor.selection.getSelectionStart(doc)),
|
917
|
+
childNodes;
|
864
918
|
|
865
919
|
// Special handling for blockquote
|
866
920
|
if (tagName === 'blockquote') {
|
867
921
|
if (blockContainer) {
|
868
|
-
|
922
|
+
childNodes = Array.prototype.slice.call(blockContainer.childNodes);
|
869
923
|
// Check if the blockquote has a block element as a child (nested blocks)
|
870
924
|
if (childNodes.some(function (childNode) {
|
871
925
|
return Util.isBlockContainer(childNode);
|
@@ -895,6 +949,28 @@ MediumEditor.extensions = {};
|
|
895
949
|
if (Util.isIE) {
|
896
950
|
tagName = '<' + tagName + '>';
|
897
951
|
}
|
952
|
+
|
953
|
+
// When FF, IE and Edge, we have to handle blockquote node seperately as 'formatblock' does not work.
|
954
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands
|
955
|
+
if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') {
|
956
|
+
// For IE, just use outdent
|
957
|
+
if (Util.isIE && tagName === '<p>') {
|
958
|
+
return doc.execCommand('outdent', false, tagName);
|
959
|
+
}
|
960
|
+
|
961
|
+
// For Firefox and Edge, make sure there's a nested block element before calling outdent
|
962
|
+
if ((Util.isFF || Util.isEdge) && tagName === 'p') {
|
963
|
+
childNodes = Array.prototype.slice.call(blockContainer.childNodes);
|
964
|
+
// If there are some non-block elements we need to wrap everything in a <p> before we outdent
|
965
|
+
if (childNodes.some(function (childNode) {
|
966
|
+
return !Util.isBlockContainer(childNode);
|
967
|
+
})) {
|
968
|
+
doc.execCommand('formatBlock', false, tagName);
|
969
|
+
}
|
970
|
+
return doc.execCommand('outdent', false, tagName);
|
971
|
+
}
|
972
|
+
}
|
973
|
+
|
898
974
|
return doc.execCommand('formatBlock', false, tagName);
|
899
975
|
},
|
900
976
|
|
@@ -923,6 +999,25 @@ MediumEditor.extensions = {};
|
|
923
999
|
}
|
924
1000
|
},
|
925
1001
|
|
1002
|
+
/*
|
1003
|
+
* this function is called to explicitly remove the target='_blank' as FF holds on to _blank value even
|
1004
|
+
* after unchecking the checkbox on anchor form
|
1005
|
+
*/
|
1006
|
+
removeTargetBlank: function (el, anchorUrl) {
|
1007
|
+
var i;
|
1008
|
+
if (el.nodeName.toLowerCase() === 'a') {
|
1009
|
+
el.removeAttribute('target');
|
1010
|
+
} else {
|
1011
|
+
el = el.getElementsByTagName('a');
|
1012
|
+
|
1013
|
+
for (i = 0; i < el.length; i += 1) {
|
1014
|
+
if (anchorUrl === el[i].attributes.href.value) {
|
1015
|
+
el[i].removeAttribute('target');
|
1016
|
+
}
|
1017
|
+
}
|
1018
|
+
}
|
1019
|
+
},
|
1020
|
+
|
926
1021
|
addClassToAnchors: function (el, buttonClass) {
|
927
1022
|
var classes = buttonClass.split(' '),
|
928
1023
|
i,
|
@@ -1228,18 +1323,30 @@ MediumEditor.extensions = {};
|
|
1228
1323
|
return element && element.nodeType !== 3 && Util.blockContainerElementNames.indexOf(element.nodeName.toLowerCase()) !== -1;
|
1229
1324
|
},
|
1230
1325
|
|
1326
|
+
/* Finds the closest ancestor which is a block container element
|
1327
|
+
* If element is within editor element but not within any other block element,
|
1328
|
+
* the editor element is returned
|
1329
|
+
*/
|
1231
1330
|
getClosestBlockContainer: function (node) {
|
1232
1331
|
return Util.traverseUp(node, function (node) {
|
1233
|
-
return Util.isBlockContainer(node);
|
1332
|
+
return Util.isBlockContainer(node) || Util.isMediumEditorElement(node);
|
1234
1333
|
});
|
1235
1334
|
},
|
1236
1335
|
|
1336
|
+
/* Finds highest level ancestor element which is a block container element
|
1337
|
+
* If element is within editor element but not within any other block element,
|
1338
|
+
* the editor element is returned
|
1339
|
+
*/
|
1237
1340
|
getTopBlockContainer: function (element) {
|
1238
|
-
var topBlock = element;
|
1341
|
+
var topBlock = Util.isBlockContainer(element) ? element : false;
|
1239
1342
|
Util.traverseUp(element, function (el) {
|
1240
1343
|
if (Util.isBlockContainer(el)) {
|
1241
1344
|
topBlock = el;
|
1242
1345
|
}
|
1346
|
+
if (!topBlock && Util.isMediumEditorElement(el)) {
|
1347
|
+
topBlock = el;
|
1348
|
+
return true;
|
1349
|
+
}
|
1243
1350
|
return false;
|
1244
1351
|
});
|
1245
1352
|
return topBlock;
|
@@ -1264,13 +1371,19 @@ MediumEditor.extensions = {};
|
|
1264
1371
|
return element;
|
1265
1372
|
},
|
1266
1373
|
|
1374
|
+
// TODO: remove getFirstTextNode AND _getFirstTextNode when jumping in 6.0.0 (no code references)
|
1267
1375
|
getFirstTextNode: function (element) {
|
1376
|
+
Util.warn('getFirstTextNode is deprecated and will be removed in version 6.0.0');
|
1377
|
+
return Util._getFirstTextNode(element);
|
1378
|
+
},
|
1379
|
+
|
1380
|
+
_getFirstTextNode: function (element) {
|
1268
1381
|
if (element.nodeType === 3) {
|
1269
1382
|
return element;
|
1270
1383
|
}
|
1271
1384
|
|
1272
1385
|
for (var i = 0; i < element.childNodes.length; i++) {
|
1273
|
-
var textNode = Util.
|
1386
|
+
var textNode = Util._getFirstTextNode(element.childNodes[i]);
|
1274
1387
|
if (textNode !== null) {
|
1275
1388
|
return textNode;
|
1276
1389
|
}
|
@@ -1666,6 +1779,21 @@ MediumEditor.extensions = {};
|
|
1666
1779
|
start: start,
|
1667
1780
|
end: start + range.toString().length
|
1668
1781
|
};
|
1782
|
+
|
1783
|
+
// Check to see if the selection starts with any images
|
1784
|
+
// if so we need to make sure the the beginning of the selection is
|
1785
|
+
// set correctly when importing selection
|
1786
|
+
if (this.doesRangeStartWithImages(range, doc)) {
|
1787
|
+
selectionState.startsWithImage = true;
|
1788
|
+
}
|
1789
|
+
|
1790
|
+
// Check to see if the selection has any trailing images
|
1791
|
+
// if so, this this means we need to look for them when we import selection
|
1792
|
+
var trailingImageCount = this.getTrailingImageCount(root, selectionState, range.endContainer, range.endOffset);
|
1793
|
+
if (trailingImageCount) {
|
1794
|
+
selectionState.trailingImageCount = trailingImageCount;
|
1795
|
+
}
|
1796
|
+
|
1669
1797
|
// If start = 0 there may still be an empty paragraph before it, but we don't care.
|
1670
1798
|
if (start !== 0) {
|
1671
1799
|
var emptyBlocksIndex = this.getIndexRelativeToAdjacentEmptyBlocks(doc, root, range.startContainer, range.startOffset);
|
@@ -1701,33 +1829,106 @@ MediumEditor.extensions = {};
|
|
1701
1829
|
nodeStack = [],
|
1702
1830
|
charIndex = 0,
|
1703
1831
|
foundStart = false,
|
1832
|
+
foundEnd = false,
|
1833
|
+
trailingImageCount = 0,
|
1704
1834
|
stop = false,
|
1705
|
-
nextCharIndex
|
1835
|
+
nextCharIndex,
|
1836
|
+
allowRangeToStartAtEndOfNode = false,
|
1837
|
+
lastTextNode = null;
|
1838
|
+
|
1839
|
+
// When importing selection, the start of the selection may lie at the end of an element
|
1840
|
+
// or at the beginning of an element. Since visually there is no difference between these 2
|
1841
|
+
// we will try to move the selection to the beginning of an element since this is generally
|
1842
|
+
// what users will expect and it's a more predictable behavior.
|
1843
|
+
//
|
1844
|
+
// However, there are some specific cases when we don't want to do this:
|
1845
|
+
// 1) We're attempting to move the cursor outside of the end of an anchor [favorLaterSelectionAnchor = true]
|
1846
|
+
// 2) The selection starts with an image, which is special since an image doesn't have any 'content'
|
1847
|
+
// as far as selection and ranges are concerned
|
1848
|
+
// 3) The selection starts after a specified number of empty block elements (selectionState.emptyBlocksIndex)
|
1849
|
+
//
|
1850
|
+
// For these cases, we want the selection to start at a very specific location, so we should NOT
|
1851
|
+
// automatically move the cursor to the beginning of the first actual chunk of text
|
1852
|
+
if (favorLaterSelectionAnchor || selectionState.startsWithImage || typeof selectionState.emptyBlocksIndex !== 'undefined') {
|
1853
|
+
allowRangeToStartAtEndOfNode = true;
|
1854
|
+
}
|
1706
1855
|
|
1707
1856
|
while (!stop && node) {
|
1708
|
-
|
1857
|
+
// Only iterate over elements and text nodes
|
1858
|
+
if (node.nodeType > 3) {
|
1859
|
+
node = nodeStack.pop();
|
1860
|
+
continue;
|
1861
|
+
}
|
1862
|
+
|
1863
|
+
// If we hit a text node, we need to add the amount of characters to the overall count
|
1864
|
+
if (node.nodeType === 3 && !foundEnd) {
|
1709
1865
|
nextCharIndex = charIndex + node.length;
|
1866
|
+
// Check if we're at or beyond the start of the selection we're importing
|
1710
1867
|
if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {
|
1711
|
-
|
1712
|
-
|
1868
|
+
// NOTE: We only want to allow a selection to start at the END of an element if
|
1869
|
+
// allowRangeToStartAtEndOfNode is true
|
1870
|
+
if (allowRangeToStartAtEndOfNode || selectionState.start < nextCharIndex) {
|
1871
|
+
range.setStart(node, selectionState.start - charIndex);
|
1872
|
+
foundStart = true;
|
1873
|
+
}
|
1874
|
+
// We're at the end of a text node where the selection could start but we shouldn't
|
1875
|
+
// make the selection start here because allowRangeToStartAtEndOfNode is false.
|
1876
|
+
// However, we should keep a reference to this node in case there aren't any more
|
1877
|
+
// text nodes after this, so that we have somewhere to import the selection to
|
1878
|
+
else {
|
1879
|
+
lastTextNode = node;
|
1880
|
+
}
|
1713
1881
|
}
|
1882
|
+
// We've found the start of the selection, check if we're at or beyond the end of the selection we're importing
|
1714
1883
|
if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
|
1715
|
-
|
1716
|
-
|
1884
|
+
if (!selectionState.trailingImageCount) {
|
1885
|
+
range.setEnd(node, selectionState.end - charIndex);
|
1886
|
+
stop = true;
|
1887
|
+
} else {
|
1888
|
+
foundEnd = true;
|
1889
|
+
}
|
1717
1890
|
}
|
1718
1891
|
charIndex = nextCharIndex;
|
1719
1892
|
} else {
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1893
|
+
if (selectionState.trailingImageCount && foundEnd) {
|
1894
|
+
if (node.nodeName.toLowerCase() === 'img') {
|
1895
|
+
trailingImageCount++;
|
1896
|
+
}
|
1897
|
+
if (trailingImageCount === selectionState.trailingImageCount) {
|
1898
|
+
// Find which index the image is in its parent's children
|
1899
|
+
var endIndex = 0;
|
1900
|
+
while (node.parentNode.childNodes[endIndex] !== node) {
|
1901
|
+
endIndex++;
|
1902
|
+
}
|
1903
|
+
range.setEnd(node.parentNode, endIndex + 1);
|
1904
|
+
stop = true;
|
1905
|
+
}
|
1906
|
+
}
|
1907
|
+
|
1908
|
+
if (!stop && node.nodeType === 1) {
|
1909
|
+
// this is an element
|
1910
|
+
// add all its children to the stack
|
1911
|
+
var i = node.childNodes.length - 1;
|
1912
|
+
while (i >= 0) {
|
1913
|
+
nodeStack.push(node.childNodes[i]);
|
1914
|
+
i -= 1;
|
1915
|
+
}
|
1724
1916
|
}
|
1725
1917
|
}
|
1918
|
+
|
1726
1919
|
if (!stop) {
|
1727
1920
|
node = nodeStack.pop();
|
1728
1921
|
}
|
1729
1922
|
}
|
1730
1923
|
|
1924
|
+
// If we've gone through the entire text but didn't find the beginning of a text node
|
1925
|
+
// to make the selection start at, we should fall back to starting the selection
|
1926
|
+
// at the END of the last text node we found
|
1927
|
+
if (!foundStart && lastTextNode) {
|
1928
|
+
range.setStart(lastTextNode, lastTextNode.length);
|
1929
|
+
range.setEnd(lastTextNode, lastTextNode.length);
|
1930
|
+
}
|
1931
|
+
|
1731
1932
|
if (typeof selectionState.emptyBlocksIndex !== 'undefined') {
|
1732
1933
|
range = this.importSelectionMoveCursorPastBlocks(doc, root, selectionState.emptyBlocksIndex, range);
|
1733
1934
|
}
|
@@ -1816,6 +2017,10 @@ MediumEditor.extensions = {};
|
|
1816
2017
|
}
|
1817
2018
|
}
|
1818
2019
|
|
2020
|
+
if (!targetNode) {
|
2021
|
+
targetNode = startBlock;
|
2022
|
+
}
|
2023
|
+
|
1819
2024
|
// We're selecting a high-level block node, so make sure the cursor gets moved into the deepest
|
1820
2025
|
// element at the beginning of the block
|
1821
2026
|
range.setStart(MediumEditor.util.getFirstSelectableLeafNode(targetNode), 0);
|
@@ -1839,8 +2044,21 @@ MediumEditor.extensions = {};
|
|
1839
2044
|
if (node.nodeType !== 3) {
|
1840
2045
|
node = cursorContainer.childNodes[cursorOffset];
|
1841
2046
|
}
|
1842
|
-
if (node
|
1843
|
-
|
2047
|
+
if (node) {
|
2048
|
+
// The element isn't at the beginning of a block, so it has content before it
|
2049
|
+
if (!MediumEditor.util.isElementAtBeginningOfBlock(node)) {
|
2050
|
+
return -1;
|
2051
|
+
}
|
2052
|
+
|
2053
|
+
var previousSibling = MediumEditor.util.findPreviousSibling(node);
|
2054
|
+
// If there is no previous sibling, this is the first text element in the editor
|
2055
|
+
if (!previousSibling) {
|
2056
|
+
return -1;
|
2057
|
+
}
|
2058
|
+
// If the previous sibling has text, then there are no empty blocks before this
|
2059
|
+
else if (previousSibling.nodeValue) {
|
2060
|
+
return -1;
|
2061
|
+
}
|
1844
2062
|
}
|
1845
2063
|
|
1846
2064
|
// Walk over block elements, counting number of empty blocks between last piece of text
|
@@ -1864,6 +2082,138 @@ MediumEditor.extensions = {};
|
|
1864
2082
|
return emptyBlocksCount;
|
1865
2083
|
},
|
1866
2084
|
|
2085
|
+
// Returns true if the selection range begins with an image tag
|
2086
|
+
// Returns false if the range starts with any non empty text nodes
|
2087
|
+
doesRangeStartWithImages: function (range, doc) {
|
2088
|
+
if (range.startOffset !== 0 || range.startContainer.nodeType !== 1) {
|
2089
|
+
return false;
|
2090
|
+
}
|
2091
|
+
|
2092
|
+
if (range.startContainer.nodeName.toLowerCase() === 'img') {
|
2093
|
+
return true;
|
2094
|
+
}
|
2095
|
+
|
2096
|
+
var img = range.startContainer.querySelector('img');
|
2097
|
+
if (!img) {
|
2098
|
+
return false;
|
2099
|
+
}
|
2100
|
+
|
2101
|
+
var treeWalker = doc.createTreeWalker(range.startContainer, NodeFilter.SHOW_ALL, null, false);
|
2102
|
+
while (treeWalker.nextNode()) {
|
2103
|
+
var next = treeWalker.currentNode;
|
2104
|
+
// If we hit the image, then there isn't any text before the image so
|
2105
|
+
// the image is at the beginning of the range
|
2106
|
+
if (next === img) {
|
2107
|
+
break;
|
2108
|
+
}
|
2109
|
+
// If we haven't hit the iamge, but found text that contains content
|
2110
|
+
// then the range doesn't start with an image
|
2111
|
+
if (next.nodeValue) {
|
2112
|
+
return false;
|
2113
|
+
}
|
2114
|
+
}
|
2115
|
+
|
2116
|
+
return true;
|
2117
|
+
},
|
2118
|
+
|
2119
|
+
getTrailingImageCount: function (root, selectionState, endContainer, endOffset) {
|
2120
|
+
// If the endOffset of a range is 0, the endContainer doesn't contain images
|
2121
|
+
// If the endContainer is a text node, there are no trailing images
|
2122
|
+
if (endOffset === 0 || endContainer.nodeType !== 1) {
|
2123
|
+
return 0;
|
2124
|
+
}
|
2125
|
+
|
2126
|
+
// If the endContainer isn't an image, and doesn't have an image descendants
|
2127
|
+
// there are no trailing images
|
2128
|
+
if (endContainer.nodeName.toLowerCase() !== 'img' && !endContainer.querySelector('img')) {
|
2129
|
+
return 0;
|
2130
|
+
}
|
2131
|
+
|
2132
|
+
var lastNode = endContainer.childNodes[endOffset - 1];
|
2133
|
+
while (lastNode.hasChildNodes()) {
|
2134
|
+
lastNode = lastNode.lastChild;
|
2135
|
+
}
|
2136
|
+
|
2137
|
+
var node = root,
|
2138
|
+
nodeStack = [],
|
2139
|
+
charIndex = 0,
|
2140
|
+
foundStart = false,
|
2141
|
+
foundEnd = false,
|
2142
|
+
stop = false,
|
2143
|
+
nextCharIndex,
|
2144
|
+
trailingImages = 0;
|
2145
|
+
|
2146
|
+
while (!stop && node) {
|
2147
|
+
// Only iterate over elements and text nodes
|
2148
|
+
if (node.nodeType > 3) {
|
2149
|
+
node = nodeStack.pop();
|
2150
|
+
continue;
|
2151
|
+
}
|
2152
|
+
|
2153
|
+
if (node.nodeType === 3 && !foundEnd) {
|
2154
|
+
trailingImages = 0;
|
2155
|
+
nextCharIndex = charIndex + node.length;
|
2156
|
+
if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) {
|
2157
|
+
foundStart = true;
|
2158
|
+
}
|
2159
|
+
if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) {
|
2160
|
+
foundEnd = true;
|
2161
|
+
}
|
2162
|
+
charIndex = nextCharIndex;
|
2163
|
+
} else {
|
2164
|
+
if (node.nodeName.toLowerCase() === 'img') {
|
2165
|
+
trailingImages++;
|
2166
|
+
}
|
2167
|
+
|
2168
|
+
if (node === lastNode) {
|
2169
|
+
stop = true;
|
2170
|
+
} else if (node.nodeType === 1) {
|
2171
|
+
// this is an element
|
2172
|
+
// add all its children to the stack
|
2173
|
+
var i = node.childNodes.length - 1;
|
2174
|
+
while (i >= 0) {
|
2175
|
+
nodeStack.push(node.childNodes[i]);
|
2176
|
+
i -= 1;
|
2177
|
+
}
|
2178
|
+
}
|
2179
|
+
}
|
2180
|
+
|
2181
|
+
if (!stop) {
|
2182
|
+
node = nodeStack.pop();
|
2183
|
+
}
|
2184
|
+
}
|
2185
|
+
|
2186
|
+
return trailingImages;
|
2187
|
+
},
|
2188
|
+
|
2189
|
+
// determine if the current selection contains any 'content'
|
2190
|
+
// content being any non-white space text or an image
|
2191
|
+
selectionContainsContent: function (doc) {
|
2192
|
+
var sel = doc.getSelection();
|
2193
|
+
|
2194
|
+
// collapsed selection or selection withour range doesn't contain content
|
2195
|
+
if (!sel || sel.isCollapsed || !sel.rangeCount) {
|
2196
|
+
return false;
|
2197
|
+
}
|
2198
|
+
|
2199
|
+
// if toString() contains any text, the selection contains some content
|
2200
|
+
if (sel.toString().trim() !== '') {
|
2201
|
+
return true;
|
2202
|
+
}
|
2203
|
+
|
2204
|
+
// if selection contains only image(s), it will return empty for toString()
|
2205
|
+
// so check for an image manually
|
2206
|
+
var selectionNode = this.getSelectedParentElement(sel.getRangeAt(0));
|
2207
|
+
if (selectionNode) {
|
2208
|
+
if (selectionNode.nodeName.toLowerCase() === 'img' ||
|
2209
|
+
(selectionNode.nodeType === 1 && selectionNode.querySelector('img'))) {
|
2210
|
+
return true;
|
2211
|
+
}
|
2212
|
+
}
|
2213
|
+
|
2214
|
+
return false;
|
2215
|
+
},
|
2216
|
+
|
1867
2217
|
selectionInContentEditableFalse: function (contentWindow) {
|
1868
2218
|
// determine if the current selection is exclusively inside
|
1869
2219
|
// a contenteditable="false", though treat the case of an
|
@@ -2004,6 +2354,20 @@ MediumEditor.extensions = {};
|
|
2004
2354
|
return range;
|
2005
2355
|
},
|
2006
2356
|
|
2357
|
+
/**
|
2358
|
+
* Clear the current highlighted selection and set the caret to the start or the end of that prior selection, defaults to end.
|
2359
|
+
*
|
2360
|
+
* @param {DomDocument} doc Current document
|
2361
|
+
* @param {boolean} moveCursorToStart A boolean representing whether or not to set the caret to the beginning of the prior selection.
|
2362
|
+
*/
|
2363
|
+
clearSelection: function (doc, moveCursorToStart) {
|
2364
|
+
if (moveCursorToStart) {
|
2365
|
+
doc.getSelection().collapseToStart();
|
2366
|
+
} else {
|
2367
|
+
doc.getSelection().collapseToEnd();
|
2368
|
+
}
|
2369
|
+
},
|
2370
|
+
|
2007
2371
|
/**
|
2008
2372
|
* Move cursor to the given node with the given offset.
|
2009
2373
|
*
|
@@ -2049,7 +2413,7 @@ MediumEditor.extensions = {};
|
|
2049
2413
|
};
|
2050
2414
|
|
2051
2415
|
Events.prototype = {
|
2052
|
-
InputEventOnContenteditableSupported: !MediumEditor.util.isIE,
|
2416
|
+
InputEventOnContenteditableSupported: !MediumEditor.util.isIE && !MediumEditor.util.isEdge,
|
2053
2417
|
|
2054
2418
|
// Helpers for event handling
|
2055
2419
|
|
@@ -2198,30 +2562,37 @@ MediumEditor.extensions = {};
|
|
2198
2562
|
return;
|
2199
2563
|
}
|
2200
2564
|
|
2565
|
+
// Helper method to call all listeners to execCommand
|
2566
|
+
var callListeners = function (args, result) {
|
2567
|
+
if (doc.execCommand.listeners) {
|
2568
|
+
doc.execCommand.listeners.forEach(function (listener) {
|
2569
|
+
listener({
|
2570
|
+
command: args[0],
|
2571
|
+
value: args[2],
|
2572
|
+
args: args,
|
2573
|
+
result: result
|
2574
|
+
});
|
2575
|
+
});
|
2576
|
+
}
|
2577
|
+
},
|
2578
|
+
|
2201
2579
|
// Create a wrapper method for execCommand which will:
|
2202
2580
|
// 1) Call document.execCommand with the correct arguments
|
2203
2581
|
// 2) Loop through any listeners and notify them that execCommand was called
|
2204
2582
|
// passing extra info on the call
|
2205
2583
|
// 3) Return the result
|
2206
|
-
|
2207
|
-
|
2584
|
+
wrapper = function () {
|
2585
|
+
var result = doc.execCommand.orig.apply(this, arguments);
|
2208
2586
|
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2587
|
+
if (!doc.execCommand.listeners) {
|
2588
|
+
return result;
|
2589
|
+
}
|
2212
2590
|
|
2213
|
-
|
2214
|
-
|
2215
|
-
listener({
|
2216
|
-
command: aCommandName,
|
2217
|
-
value: aValueArgument,
|
2218
|
-
args: args,
|
2219
|
-
result: result
|
2220
|
-
});
|
2221
|
-
});
|
2591
|
+
var args = Array.prototype.slice.call(arguments);
|
2592
|
+
callListeners(args, result);
|
2222
2593
|
|
2223
|
-
|
2224
|
-
|
2594
|
+
return result;
|
2595
|
+
};
|
2225
2596
|
|
2226
2597
|
// Store a reference to the original execCommand
|
2227
2598
|
wrapper.orig = doc.execCommand;
|
@@ -2229,6 +2600,9 @@ MediumEditor.extensions = {};
|
|
2229
2600
|
// Attach an array for storing listeners
|
2230
2601
|
wrapper.listeners = [];
|
2231
2602
|
|
2603
|
+
// Helper for notifying listeners
|
2604
|
+
wrapper.callListeners = callListeners;
|
2605
|
+
|
2232
2606
|
// Overwrite execCommand
|
2233
2607
|
doc.execCommand = wrapper;
|
2234
2608
|
},
|
@@ -3049,6 +3423,11 @@ MediumEditor.extensions = {};
|
|
3049
3423
|
formSaveLabel: '✓',
|
3050
3424
|
formCloseLabel: '×',
|
3051
3425
|
|
3426
|
+
/* activeClass: [string]
|
3427
|
+
* set class which added to shown form
|
3428
|
+
*/
|
3429
|
+
activeClass: 'medium-editor-toolbar-form-active',
|
3430
|
+
|
3052
3431
|
/* hasForm: [boolean]
|
3053
3432
|
*
|
3054
3433
|
* Setting this to true will cause getForm() to be called
|
@@ -3071,14 +3450,34 @@ MediumEditor.extensions = {};
|
|
3071
3450
|
* This function should return true/false reflecting
|
3072
3451
|
* whether the form is currently displayed
|
3073
3452
|
*/
|
3074
|
-
isDisplayed: function () {
|
3453
|
+
isDisplayed: function () {
|
3454
|
+
if (this.hasForm) {
|
3455
|
+
return this.getForm().classList.contains(this.activeClass);
|
3456
|
+
}
|
3457
|
+
return false;
|
3458
|
+
},
|
3459
|
+
|
3460
|
+
/* hideForm: [function ()]
|
3461
|
+
*
|
3462
|
+
* This function should show the form element inside
|
3463
|
+
* the toolbar container
|
3464
|
+
*/
|
3465
|
+
showForm: function () {
|
3466
|
+
if (this.hasForm) {
|
3467
|
+
this.getForm().classList.add(this.activeClass);
|
3468
|
+
}
|
3469
|
+
},
|
3075
3470
|
|
3076
3471
|
/* hideForm: [function ()]
|
3077
3472
|
*
|
3078
3473
|
* This function should hide the form element inside
|
3079
3474
|
* the toolbar container
|
3080
3475
|
*/
|
3081
|
-
hideForm: function () {
|
3476
|
+
hideForm: function () {
|
3477
|
+
if (this.hasForm) {
|
3478
|
+
this.getForm().classList.remove(this.activeClass);
|
3479
|
+
}
|
3480
|
+
},
|
3082
3481
|
|
3083
3482
|
/************************ Helpers ************************
|
3084
3483
|
* The following are helpers that are either set by MediumEditor
|
@@ -3267,11 +3666,11 @@ MediumEditor.extensions = {};
|
|
3267
3666
|
|
3268
3667
|
// Used by medium-editor when the default toolbar is to be displayed
|
3269
3668
|
isDisplayed: function () {
|
3270
|
-
return
|
3669
|
+
return MediumEditor.extensions.form.prototype.isDisplayed.apply(this);
|
3271
3670
|
},
|
3272
3671
|
|
3273
3672
|
hideForm: function () {
|
3274
|
-
|
3673
|
+
MediumEditor.extensions.form.prototype.hideForm.apply(this);
|
3275
3674
|
this.getInput().value = '';
|
3276
3675
|
},
|
3277
3676
|
|
@@ -3291,7 +3690,7 @@ MediumEditor.extensions = {};
|
|
3291
3690
|
|
3292
3691
|
this.base.saveSelection();
|
3293
3692
|
this.hideToolbarDefaultActions();
|
3294
|
-
|
3693
|
+
MediumEditor.extensions.form.prototype.showForm.apply(this);
|
3295
3694
|
this.setToolbarPosition();
|
3296
3695
|
|
3297
3696
|
input.value = opts.url;
|
@@ -3362,8 +3761,18 @@ MediumEditor.extensions = {};
|
|
3362
3761
|
},
|
3363
3762
|
|
3364
3763
|
checkLinkFormat: function (value) {
|
3365
|
-
|
3366
|
-
|
3764
|
+
// Matches any alphabetical characters followed by ://
|
3765
|
+
// Matches protocol relative "//"
|
3766
|
+
// Matches common external protocols "mailto:" "tel:" "maps:"
|
3767
|
+
var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):/i,
|
3768
|
+
// var te is a regex for checking if the string is a telephone number
|
3769
|
+
telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/;
|
3770
|
+
if (telRegex.test(value)) {
|
3771
|
+
return 'tel:' + value;
|
3772
|
+
} else {
|
3773
|
+
// Check for URL scheme and default to http:// if none found
|
3774
|
+
return (urlSchemeRegex.test(value) ? '' : 'http://') + value;
|
3775
|
+
}
|
3367
3776
|
},
|
3368
3777
|
|
3369
3778
|
doFormCancel: function () {
|
@@ -3564,12 +3973,16 @@ MediumEditor.extensions = {};
|
|
3564
3973
|
defaultLeft = diffLeft - halfOffsetWidth;
|
3565
3974
|
|
3566
3975
|
this.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - diffTop + this.window.pageYOffset - this.anchorPreview.offsetHeight) + 'px';
|
3976
|
+
this.anchorPreview.style.right = 'initial';
|
3567
3977
|
if (middleBoundary < halfOffsetWidth) {
|
3568
3978
|
this.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
|
3979
|
+
this.anchorPreview.style.right = 'initial';
|
3569
3980
|
} else if ((this.window.innerWidth - middleBoundary) < halfOffsetWidth) {
|
3570
|
-
this.anchorPreview.style.left =
|
3981
|
+
this.anchorPreview.style.left = 'auto';
|
3982
|
+
this.anchorPreview.style.right = 0;
|
3571
3983
|
} else {
|
3572
3984
|
this.anchorPreview.style.left = defaultLeft + middleBoundary + 'px';
|
3985
|
+
this.anchorPreview.style.right = 'initial';
|
3573
3986
|
}
|
3574
3987
|
},
|
3575
3988
|
|
@@ -3751,9 +4164,20 @@ MediumEditor.extensions = {};
|
|
3751
4164
|
this.document.execCommand('AutoUrlDetect', false, false);
|
3752
4165
|
},
|
3753
4166
|
|
4167
|
+
isLastInstance: function () {
|
4168
|
+
var activeInstances = 0;
|
4169
|
+
for (var i = 0; i < this.window._mediumEditors.length; i++) {
|
4170
|
+
var editor = this.window._mediumEditors[i];
|
4171
|
+
if (editor !== null && editor.getExtensionByName('autoLink') !== undefined) {
|
4172
|
+
activeInstances++;
|
4173
|
+
}
|
4174
|
+
}
|
4175
|
+
return activeInstances === 1;
|
4176
|
+
},
|
4177
|
+
|
3754
4178
|
destroy: function () {
|
3755
4179
|
// Turn AutoUrlDetect back on
|
3756
|
-
if (this.document.queryCommandSupported('AutoUrlDetect')) {
|
4180
|
+
if (this.document.queryCommandSupported('AutoUrlDetect') && this.isLastInstance()) {
|
3757
4181
|
this.document.execCommand('AutoUrlDetect', false, true);
|
3758
4182
|
}
|
3759
4183
|
},
|
@@ -3818,6 +4242,7 @@ MediumEditor.extensions = {};
|
|
3818
4242
|
documentModified = this.removeObsoleteAutoLinkSpans(blockElements[i]) || documentModified;
|
3819
4243
|
documentModified = this.performLinkingWithinElement(blockElements[i]) || documentModified;
|
3820
4244
|
}
|
4245
|
+
this.base.events.updateInput(contenteditable, { target: contenteditable, currentTarget: contenteditable });
|
3821
4246
|
return documentModified;
|
3822
4247
|
},
|
3823
4248
|
|
@@ -3992,7 +4417,12 @@ MediumEditor.extensions = {};
|
|
3992
4417
|
// Prevent file from opening in the current window
|
3993
4418
|
event.preventDefault();
|
3994
4419
|
event.stopPropagation();
|
3995
|
-
|
4420
|
+
// Select the dropping target, and set the selection to the end of the target
|
4421
|
+
// https://github.com/yabwe/medium-editor/issues/980
|
4422
|
+
this.base.selectElement(event.target);
|
4423
|
+
var selection = this.base.exportSelection();
|
4424
|
+
selection.start = selection.end;
|
4425
|
+
this.base.importSelection(selection);
|
3996
4426
|
// IE9 does not support the File API, so prevent file from opening in the window
|
3997
4427
|
// but also don't try to actually get the file
|
3998
4428
|
if (event.dataTransfer.files) {
|
@@ -4016,20 +4446,18 @@ MediumEditor.extensions = {};
|
|
4016
4446
|
},
|
4017
4447
|
|
4018
4448
|
insertImageFile: function (file) {
|
4449
|
+
if (typeof FileReader !== 'function') {
|
4450
|
+
return;
|
4451
|
+
}
|
4019
4452
|
var fileReader = new FileReader();
|
4020
4453
|
fileReader.readAsDataURL(file);
|
4021
4454
|
|
4022
|
-
|
4023
|
-
|
4024
|
-
|
4025
|
-
|
4026
|
-
|
4027
|
-
|
4028
|
-
img.removeAttribute('id');
|
4029
|
-
img.removeAttribute('class');
|
4030
|
-
img.src = fileReader.result;
|
4031
|
-
}
|
4032
|
-
}.bind(this);
|
4455
|
+
// attach the onload event handler, makes it easier to listen in with jasmine
|
4456
|
+
fileReader.addEventListener('load', function (e) {
|
4457
|
+
var addImageElement = this.document.createElement('img');
|
4458
|
+
addImageElement.src = e.target.result;
|
4459
|
+
MediumEditor.util.insertHTMLCommand(this.document, addImageElement.outerHTML);
|
4460
|
+
}.bind(this));
|
4033
4461
|
}
|
4034
4462
|
});
|
4035
4463
|
|
@@ -4545,9 +4973,17 @@ MediumEditor.extensions = {};
|
|
4545
4973
|
*/
|
4546
4974
|
cleanPastedHTML: false,
|
4547
4975
|
|
4976
|
+
/* preCleanReplacements: [Array]
|
4977
|
+
* custom pairs (2 element arrays) of RegExp and replacement text to use during past when
|
4978
|
+
* __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method.
|
4979
|
+
* These replacements are executed before any medium editor defined replacements.
|
4980
|
+
*/
|
4981
|
+
preCleanReplacements: [],
|
4982
|
+
|
4548
4983
|
/* cleanReplacements: [Array]
|
4549
4984
|
* custom pairs (2 element arrays) of RegExp and replacement text to use during paste when
|
4550
4985
|
* __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method.
|
4986
|
+
* These replacements are executed after any medium editor defined replacements.
|
4551
4987
|
*/
|
4552
4988
|
cleanReplacements: [],
|
4553
4989
|
|
@@ -4625,7 +5061,10 @@ MediumEditor.extensions = {};
|
|
4625
5061
|
cleanPaste: function (text) {
|
4626
5062
|
var i, elList, tmp, workEl,
|
4627
5063
|
multiline = /<p|<br|<div/.test(text),
|
4628
|
-
replacements =
|
5064
|
+
replacements = [].concat(
|
5065
|
+
this.preCleanReplacements || [],
|
5066
|
+
createReplacements(),
|
5067
|
+
this.cleanReplacements || []);
|
4629
5068
|
|
4630
5069
|
for (i = 0; i < replacements.length; i += 1) {
|
4631
5070
|
text = text.replace(replacements[i][0], replacements[i][1]);
|
@@ -4934,6 +5373,11 @@ MediumEditor.extensions = {};
|
|
4934
5373
|
*/
|
4935
5374
|
sticky: false,
|
4936
5375
|
|
5376
|
+
/* stickyTopOffset: [Number]
|
5377
|
+
* Value in pixel of the top offset above the toolbar
|
5378
|
+
*/
|
5379
|
+
stickyTopOffset: 0,
|
5380
|
+
|
4937
5381
|
/* updateOnEmptySelection: [boolean]
|
4938
5382
|
* When the __static__ option is true, this enables/disables updating
|
4939
5383
|
* the state of the toolbar buttons even when the selection is collapsed
|
@@ -5282,7 +5726,7 @@ MediumEditor.extensions = {};
|
|
5282
5726
|
}
|
5283
5727
|
|
5284
5728
|
// If we don't have a 'valid' selection -> hide toolbar
|
5285
|
-
if (
|
5729
|
+
if (!MediumEditor.selection.selectionContainsContent(this.document) ||
|
5286
5730
|
(this.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {
|
5287
5731
|
return this.hideToolbar();
|
5288
5732
|
}
|
@@ -5426,15 +5870,13 @@ MediumEditor.extensions = {};
|
|
5426
5870
|
|
5427
5871
|
if (this.sticky) {
|
5428
5872
|
// If it's beyond the height of the editor, position it at the bottom of the editor
|
5429
|
-
if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight)) {
|
5873
|
+
if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) {
|
5430
5874
|
toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
|
5431
5875
|
toolbarElement.classList.remove('medium-editor-sticky-toolbar');
|
5432
|
-
|
5433
5876
|
// Stick the toolbar to the top of the window
|
5434
|
-
} else if (scrollTop > (containerTop - toolbarHeight)) {
|
5877
|
+
} else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) {
|
5435
5878
|
toolbarElement.classList.add('medium-editor-sticky-toolbar');
|
5436
|
-
toolbarElement.style.top = '
|
5437
|
-
|
5879
|
+
toolbarElement.style.top = this.stickyTopOffset + 'px';
|
5438
5880
|
// Normal static toolbar position
|
5439
5881
|
} else {
|
5440
5882
|
toolbarElement.classList.remove('medium-editor-sticky-toolbar');
|
@@ -5470,10 +5912,22 @@ MediumEditor.extensions = {};
|
|
5470
5912
|
positionToolbar: function (selection) {
|
5471
5913
|
// position the toolbar at left 0, so we can get the real width of the toolbar
|
5472
5914
|
this.getToolbarElement().style.left = '0';
|
5915
|
+
this.getToolbarElement().style.right = 'initial';
|
5916
|
+
|
5917
|
+
var range = selection.getRangeAt(0),
|
5918
|
+
boundary = range.getBoundingClientRect();
|
5919
|
+
|
5920
|
+
// Handle selections with just images
|
5921
|
+
if (!boundary || ((boundary.height === 0 && boundary.width === 0) && range.startContainer === range.endContainer)) {
|
5922
|
+
// If there's a nested image, use that for the bounding rectangle
|
5923
|
+
if (range.startContainer.nodeType === 1 && range.startContainer.querySelector('img')) {
|
5924
|
+
boundary = range.startContainer.querySelector('img').getBoundingClientRect();
|
5925
|
+
} else {
|
5926
|
+
boundary = range.startContainer.getBoundingClientRect();
|
5927
|
+
}
|
5928
|
+
}
|
5473
5929
|
|
5474
5930
|
var windowWidth = this.window.innerWidth,
|
5475
|
-
range = selection.getRangeAt(0),
|
5476
|
-
boundary = range.getBoundingClientRect(),
|
5477
5931
|
middleBoundary = (boundary.left + boundary.right) / 2,
|
5478
5932
|
toolbarElement = this.getToolbarElement(),
|
5479
5933
|
toolbarHeight = toolbarElement.offsetHeight,
|
@@ -5494,10 +5948,13 @@ MediumEditor.extensions = {};
|
|
5494
5948
|
|
5495
5949
|
if (middleBoundary < halfOffsetWidth) {
|
5496
5950
|
toolbarElement.style.left = defaultLeft + halfOffsetWidth + 'px';
|
5951
|
+
toolbarElement.style.right = 'initial';
|
5497
5952
|
} else if ((windowWidth - middleBoundary) < halfOffsetWidth) {
|
5498
|
-
toolbarElement.style.left =
|
5953
|
+
toolbarElement.style.left = 'auto';
|
5954
|
+
toolbarElement.style.right = 0;
|
5499
5955
|
} else {
|
5500
5956
|
toolbarElement.style.left = defaultLeft + middleBoundary + 'px';
|
5957
|
+
toolbarElement.style.right = 'initial';
|
5501
5958
|
}
|
5502
5959
|
}
|
5503
5960
|
});
|
@@ -5575,7 +6032,7 @@ MediumEditor.extensions = {};
|
|
5575
6032
|
textContent = node.textContent,
|
5576
6033
|
caretPositions = MediumEditor.selection.getCaretOffsets(node);
|
5577
6034
|
|
5578
|
-
if ((textContent[caretPositions.left - 1] === undefined) || (textContent[caretPositions.left - 1] === '
|
6035
|
+
if ((textContent[caretPositions.left - 1] === undefined) || (textContent[caretPositions.left - 1].trim() === '') || (textContent[caretPositions.left] !== undefined && textContent[caretPositions.left].trim() === '')) {
|
5579
6036
|
event.preventDefault();
|
5580
6037
|
}
|
5581
6038
|
}
|
@@ -5637,7 +6094,7 @@ MediumEditor.extensions = {};
|
|
5637
6094
|
// instead delete previous node and cancel the event.
|
5638
6095
|
node.previousElementSibling.parentNode.removeChild(node.previousElementSibling);
|
5639
6096
|
event.preventDefault();
|
5640
|
-
} else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) {
|
6097
|
+
} else if (!this.options.disableDoubleReturn && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) {
|
5641
6098
|
// hitting return in the begining of a header will create empty header elements before the current one
|
5642
6099
|
// instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph
|
5643
6100
|
p = this.options.ownerDocument.createElement('p');
|
@@ -5697,6 +6154,14 @@ MediumEditor.extensions = {};
|
|
5697
6154
|
node.parentElement.removeChild(node);
|
5698
6155
|
|
5699
6156
|
event.preventDefault();
|
6157
|
+
} else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) &&
|
6158
|
+
(MediumEditor.util.getClosestTag(node, 'blockquote') !== false) &&
|
6159
|
+
MediumEditor.selection.getCaretOffsets(node).left === 0) {
|
6160
|
+
|
6161
|
+
// when cursor is at the begining of the element and the element is <blockquote>
|
6162
|
+
// then pressing backspace key should change the <blockquote> to a <p> tag
|
6163
|
+
event.preventDefault();
|
6164
|
+
MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p');
|
5700
6165
|
}
|
5701
6166
|
}
|
5702
6167
|
|
@@ -5712,16 +6177,19 @@ MediumEditor.extensions = {};
|
|
5712
6177
|
this.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
5713
6178
|
}
|
5714
6179
|
|
5715
|
-
|
6180
|
+
// https://github.com/yabwe/medium-editor/issues/834
|
6181
|
+
// https://github.com/yabwe/medium-editor/pull/382
|
6182
|
+
// Don't call format block if this is a block element (ie h1, figCaption, etc.)
|
6183
|
+
if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) &&
|
6184
|
+
!MediumEditor.util.isListItem(node) &&
|
6185
|
+
!MediumEditor.util.isBlockContainer(node)) {
|
6186
|
+
|
5716
6187
|
tagName = node.nodeName.toLowerCase();
|
5717
6188
|
// For anchor tags, unlink
|
5718
6189
|
if (tagName === 'a') {
|
5719
6190
|
this.options.ownerDocument.execCommand('unlink', false, null);
|
5720
6191
|
} else if (!event.shiftKey && !event.ctrlKey) {
|
5721
|
-
|
5722
|
-
if (!/h\d/.test(tagName)) {
|
5723
|
-
this.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
5724
|
-
}
|
6192
|
+
this.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
5725
6193
|
}
|
5726
6194
|
}
|
5727
6195
|
}
|
@@ -5897,6 +6365,8 @@ MediumEditor.extensions = {};
|
|
5897
6365
|
}
|
5898
6366
|
|
5899
6367
|
function initElements() {
|
6368
|
+
var isTextareaUsed = false;
|
6369
|
+
|
5900
6370
|
this.elements.forEach(function (element, index) {
|
5901
6371
|
if (!this.options.disableEditing && !element.getAttribute('data-disable-editing')) {
|
5902
6372
|
element.setAttribute('contentEditable', true);
|
@@ -5908,15 +6378,18 @@ MediumEditor.extensions = {};
|
|
5908
6378
|
element.setAttribute('medium-editor-index', index);
|
5909
6379
|
|
5910
6380
|
if (element.hasAttribute('medium-editor-textarea-id')) {
|
5911
|
-
|
5912
|
-
var target = event.target,
|
5913
|
-
textarea = target.parentNode.querySelector('textarea[medium-editor-textarea-id="' + target.getAttribute('medium-editor-textarea-id') + '"]');
|
5914
|
-
if (textarea) {
|
5915
|
-
textarea.value = this.serialize()[target.id].value;
|
5916
|
-
}
|
5917
|
-
}.bind(this));
|
6381
|
+
isTextareaUsed = true;
|
5918
6382
|
}
|
5919
6383
|
}, this);
|
6384
|
+
|
6385
|
+
if (isTextareaUsed) {
|
6386
|
+
this.subscribe('editableInput', function (event, editable) {
|
6387
|
+
var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]');
|
6388
|
+
if (textarea) {
|
6389
|
+
textarea.value = this.serialize()[editable.id].value;
|
6390
|
+
}
|
6391
|
+
}.bind(this));
|
6392
|
+
}
|
5920
6393
|
}
|
5921
6394
|
|
5922
6395
|
function attachHandlers() {
|
@@ -6069,7 +6542,8 @@ MediumEditor.extensions = {};
|
|
6069
6542
|
}
|
6070
6543
|
|
6071
6544
|
if (action === 'image') {
|
6072
|
-
|
6545
|
+
var src = this.options.contentWindow.getSelection().toString().trim();
|
6546
|
+
return this.options.ownerDocument.execCommand('insertImage', false, src);
|
6073
6547
|
}
|
6074
6548
|
|
6075
6549
|
/* Issue: https://github.com/yabwe/medium-editor/issues/595
|
@@ -6519,7 +6993,7 @@ MediumEditor.extensions = {};
|
|
6519
6993
|
// but the selection is contained within the same block element
|
6520
6994
|
// we want to make sure we create a single link, and not multiple links
|
6521
6995
|
// which can happen with the built in browser functionality
|
6522
|
-
if (commonAncestorContainer.nodeType !== 3 && startContainerParentElement === endContainerParentElement) {
|
6996
|
+
if (commonAncestorContainer.nodeType !== 3 && commonAncestorContainer.textContent.length !== 0 && startContainerParentElement === endContainerParentElement) {
|
6523
6997
|
var parentElement = (startContainerParentElement || currentEditor),
|
6524
6998
|
fragment = this.options.ownerDocument.createDocumentFragment();
|
6525
6999
|
|
@@ -6598,6 +7072,8 @@ MediumEditor.extensions = {};
|
|
6598
7072
|
|
6599
7073
|
if (this.options.targetBlank || opts.target === '_blank') {
|
6600
7074
|
MediumEditor.util.setTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), opts.url);
|
7075
|
+
} else {
|
7076
|
+
MediumEditor.util.removeTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), opts.url);
|
6601
7077
|
}
|
6602
7078
|
|
6603
7079
|
if (opts.buttonClass) {
|
@@ -6678,7 +7154,7 @@ MediumEditor.parseVersionString = function (release) {
|
|
6678
7154
|
|
6679
7155
|
MediumEditor.version = MediumEditor.parseVersionString.call(this, ({
|
6680
7156
|
// grunt-bump looks for this:
|
6681
|
-
'version': '5.
|
7157
|
+
'version': '5.15.0'
|
6682
7158
|
}).version);
|
6683
7159
|
|
6684
7160
|
return MediumEditor;
|
@@ -17,8 +17,8 @@
|
|
17
17
|
border-right: 1px solid #9ccea6;
|
18
18
|
background-color: transparent;
|
19
19
|
color: #fff;
|
20
|
-
-webkit-transition: background-color
|
21
|
-
transition: background-color
|
20
|
+
-webkit-transition: background-color .2s ease-in, color .2s ease-in;
|
21
|
+
transition: background-color .2s ease-in, color .2s ease-in; }
|
22
22
|
.medium-editor-toolbar li button:hover {
|
23
23
|
background-color: #346a3f;
|
24
24
|
color: #fff; }
|
@@ -89,7 +89,9 @@
|
|
89
89
|
left: 0;
|
90
90
|
position: absolute;
|
91
91
|
top: 0;
|
92
|
-
white-space: pre;
|
92
|
+
white-space: pre;
|
93
|
+
padding: inherit;
|
94
|
+
margin: inherit; }
|
93
95
|
|
94
96
|
.medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before {
|
95
97
|
border-style: solid;
|
@@ -196,6 +198,9 @@
|
|
196
198
|
margin: 0 10px;
|
197
199
|
text-decoration: none; }
|
198
200
|
|
201
|
+
.medium-editor-toolbar-form-active {
|
202
|
+
display: block; }
|
203
|
+
|
199
204
|
.medium-editor-toolbar-actions:after {
|
200
205
|
clear: both;
|
201
206
|
content: "";
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scrivito_editors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scrivito
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jquery-ui-rails
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - '='
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.3.0.rc1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - '='
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.3.0.rc1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: scrivito_sdk
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
61
|
+
version: 1.3.0.rc1
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
68
|
+
version: 1.3.0.rc1
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: coffee-rails
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|