scrivito_editors 1.2.0 → 1.3.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|