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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0a7830c14e8fa7edae49655b70760ef7204c5ec
4
- data.tar.gz: 9d83a494684ca1e403464dbbcc5f55314df9db75
3
+ metadata.gz: 1e6fb4fb773e3fffc14b9b3b7df14336507aace3
4
+ data.tar.gz: a315c79989d708d0e9b3f936eb56044ba82319c2
5
5
  SHA512:
6
- metadata.gz: 01efcfe8598b71be0dea504e347e8ca66c0352addb6a37dbfbed5e6317b9c82092a6758320bd68b2877cb44926d7ee2424963597f35ed8138034b68108925d2b
7
- data.tar.gz: 61f3d9f52f7be9f5fadc58efb07c183c971b1d65f236a392df570d21587c3e8ca154ff1199aa3cbccd5f6d9d1e03dbca59f951d7c5654620754cb758da224ada
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:after { content: "\F03c"; }
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:after {
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.SHOW_TEXT, null, false),
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 (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) {
563
- startReached = true;
564
- newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex);
565
- }
566
- if (startReached) {
567
- Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex);
568
- }
569
- if (startReached && currentTextIndex === match.end) {
570
- break; // Found the node(s) corresponding to the link. Break out and move on to the next.
571
- } else if (startReached && currentTextIndex > (match.end + 1)) {
572
- throw new Error('PerformLinking overshot the target!'); // should never happen...
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
- if (startReached) {
576
- matchedNodes.push(newNode || currentNode);
577
- }
583
+ if (startReached) {
584
+ matchedNodes.push(newNode || currentNode);
585
+ }
578
586
 
579
- currentTextIndex += currentNode.nodeValue.length;
580
- if (newNode !== null) {
581
- currentTextIndex += newNode.nodeValue.length;
582
- // Skip the newNode as we'll already have pushed it to the matches
583
- treeWalker.nextNode();
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
- if (doc.queryCommandSupported('insertHTML')) {
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('insertHTML', false, html);
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
- var childNodes = Array.prototype.slice.call(blockContainer.childNodes);
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.getFirstTextNode(element.childNodes[i]);
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
- if (node.nodeType === 3) {
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
- range.setStart(node, selectionState.start - charIndex);
1712
- foundStart = true;
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
- range.setEnd(node, selectionState.end - charIndex);
1716
- stop = true;
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
- var i = node.childNodes.length - 1;
1721
- while (i >= 0) {
1722
- nodeStack.push(node.childNodes[i]);
1723
- i -= 1;
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 && !MediumEditor.util.isElementAtBeginningOfBlock(node)) {
1843
- return -1;
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
- var wrapper = function (aCommandName, aShowDefaultUI, aValueArgument) {
2207
- var result = doc.execCommand.orig.apply(this, arguments);
2584
+ wrapper = function () {
2585
+ var result = doc.execCommand.orig.apply(this, arguments);
2208
2586
 
2209
- if (!doc.execCommand.listeners) {
2210
- return result;
2211
- }
2587
+ if (!doc.execCommand.listeners) {
2588
+ return result;
2589
+ }
2212
2590
 
2213
- var args = Array.prototype.slice.call(arguments);
2214
- doc.execCommand.listeners.forEach(function (listener) {
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
- return result;
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: '&#10003;',
3050
3424
  formCloseLabel: '&times;',
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 this.getForm().style.display === 'block';
3669
+ return MediumEditor.extensions.form.prototype.isDisplayed.apply(this);
3271
3670
  },
3272
3671
 
3273
3672
  hideForm: function () {
3274
- this.getForm().style.display = 'none';
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
- this.getForm().style.display = 'block';
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
- var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/;
3366
- return (re.test(value) ? '' : 'http://') + value;
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 = this.window.innerWidth + defaultLeft - halfOffsetWidth + 'px';
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
- var id = 'medium-img-' + (+new Date());
4023
- MediumEditor.util.insertHTMLCommand(this.document, '<img class="medium-editor-image-loading" id="' + id + '" />');
4024
-
4025
- fileReader.onload = function () {
4026
- var img = this.document.getElementById(id);
4027
- if (img) {
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 = createReplacements().concat(this.cleanReplacements || []);
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 (this.window.getSelection().toString().trim() === '' ||
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 = '0px';
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 = windowWidth + defaultLeft - halfOffsetWidth + 'px';
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] === ' ') || (textContent[caretPositions.left] === undefined)) {
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
- if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && !MediumEditor.util.isListItem(node)) {
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
- // only format block if this is not a header tag
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
- this.on(element, 'input', function (event) {
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
- return this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
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.10.0'
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 0.2s ease-in, color 0.2s ease-in;
21
- transition: background-color 0.2s ease-in, color 0.2s ease-in; }
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.2.0
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-03-14 00:00:00.000000000 Z
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.2.0
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.2.0
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.2.0
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.2.0
68
+ version: 1.3.0.rc1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: coffee-rails
71
71
  requirement: !ruby/object:Gem::Requirement