diff options
Diffstat (limited to 'sources/core/editable.js')
-rw-r--r-- | sources/core/editable.js | 267 |
1 files changed, 198 insertions, 69 deletions
diff --git a/sources/core/editable.js b/sources/core/editable.js index b9b0270..6b3fa9f 100644 --- a/sources/core/editable.js +++ b/sources/core/editable.js | |||
@@ -1,9 +1,12 @@ | |||
1 | /** | 1 | /** |
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. | 2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license |
4 | */ | 4 | */ |
5 | 5 | ||
6 | ( function() { | 6 | ( function() { |
7 | var isNotWhitespace, isNotBookmark, isEmpty, isBogus, emptyParagraphRegexp, | ||
8 | insert, fixTableAfterContentsDeletion, fixListAfterContentsDelete, getHtmlFromRangeHelpers, extractHtmlFromRangeHelpers; | ||
9 | |||
7 | /** | 10 | /** |
8 | * Editable class which provides all editing related activities by | 11 | * Editable class which provides all editing related activities by |
9 | * the `contenteditable` element, dynamically get attached to editor instance. | 12 | * the `contenteditable` element, dynamically get attached to editor instance. |
@@ -71,17 +74,36 @@ | |||
71 | } | 74 | } |
72 | } | 75 | } |
73 | 76 | ||
74 | // [IE] Use instead "setActive" method to focus the editable if it belongs to | 77 | // [Edge] Starting from EdgeHTML 14.14393, it does not support `setActive`. We need to use focus which |
75 | // the host page document, to avoid bringing an unexpected scroll. | 78 | // causes unexpected scroll. Store scrollTop value so it can be restored after focusing editor. |
79 | // Scroll only happens if the editor is focused for the first time. (http://dev.ckeditor.com/ticket/14825) | ||
80 | if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 && !this.hasFocus && this.getDocument().equals( CKEDITOR.document ) ) { | ||
81 | this.editor._.previousScrollTop = this.$.scrollTop; | ||
82 | } | ||
83 | |||
84 | // [IE] Use instead "setActive" method to focus the editable if it belongs to the host page document, | ||
85 | // to avoid bringing an unexpected scroll. | ||
76 | try { | 86 | try { |
77 | this.$[ CKEDITOR.env.ie && this.getDocument().equals( CKEDITOR.document ) ? 'setActive' : 'focus' ](); | 87 | if ( CKEDITOR.env.ie && !( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) && this.getDocument().equals( CKEDITOR.document ) ) { |
88 | this.$.setActive(); | ||
89 | } else { | ||
90 | // We have no control over exactly what happens when the native `focus` method is called, | ||
91 | // so save the scroll position and restore it later. | ||
92 | if ( CKEDITOR.env.chrome ) { | ||
93 | var scrollPos = this.$.scrollTop; | ||
94 | this.$.focus(); | ||
95 | this.$.scrollTop = scrollPos; | ||
96 | } else { | ||
97 | this.$.focus(); | ||
98 | } | ||
99 | } | ||
78 | } catch ( e ) { | 100 | } catch ( e ) { |
79 | // IE throws unspecified error when focusing editable after closing dialog opened on nested editable. | 101 | // IE throws unspecified error when focusing editable after closing dialog opened on nested editable. |
80 | if ( !CKEDITOR.env.ie ) | 102 | if ( !CKEDITOR.env.ie ) |
81 | throw e; | 103 | throw e; |
82 | } | 104 | } |
83 | 105 | ||
84 | // Remedy if Safari doens't applies focus properly. (#279) | 106 | // Remedy if Safari doens't applies focus properly. (http://dev.ckeditor.com/ticket/279) |
85 | if ( CKEDITOR.env.safari && !this.isInline() ) { | 107 | if ( CKEDITOR.env.safari && !this.isInline() ) { |
86 | active = CKEDITOR.document.getActive(); | 108 | active = CKEDITOR.document.getActive(); |
87 | if ( !active.equals( this.getWindow().getFrame() ) ) | 109 | if ( !active.equals( this.getWindow().getFrame() ) ) |
@@ -103,7 +125,7 @@ | |||
103 | 125 | ||
104 | // The "focusin/focusout" events bubbled, e.g. If there are elements with layout | 126 | // The "focusin/focusout" events bubbled, e.g. If there are elements with layout |
105 | // they fire this event when clicking in to edit them but it must be ignored | 127 | // they fire this event when clicking in to edit them but it must be ignored |
106 | // to allow edit their contents. (#4682) | 128 | // to allow edit their contents. (http://dev.ckeditor.com/ticket/4682) |
107 | fn = isNotBubbling( fn, this ); | 129 | fn = isNotBubbling( fn, this ); |
108 | args[ 0 ] = name; | 130 | args[ 0 ] = name; |
109 | args[ 1 ] = fn; | 131 | args[ 1 ] = fn; |
@@ -238,7 +260,7 @@ | |||
238 | * @param {String} text | 260 | * @param {String} text |
239 | */ | 261 | */ |
240 | insertText: function( text ) { | 262 | insertText: function( text ) { |
241 | // Focus the editor before calling transformPlainTextToHtml. (#12726) | 263 | // Focus the editor before calling transformPlainTextToHtml. (http://dev.ckeditor.com/ticket/12726) |
242 | this.editor.focus(); | 264 | this.editor.focus(); |
243 | this.insertHtml( this.transformPlainTextToHtml( text ), 'text' ); | 265 | this.insertHtml( this.transformPlainTextToHtml( text ), 'text' ); |
244 | }, | 266 | }, |
@@ -336,7 +358,7 @@ | |||
336 | insertElement: function( element, range ) { | 358 | insertElement: function( element, range ) { |
337 | var editor = this.editor; | 359 | var editor = this.editor; |
338 | 360 | ||
339 | // Prepare for the insertion. For example - focus editor (#11848). | 361 | // Prepare for the insertion. For example - focus editor (http://dev.ckeditor.com/ticket/11848). |
340 | editor.focus(); | 362 | editor.focus(); |
341 | editor.fire( 'saveSnapshot' ); | 363 | editor.fire( 'saveSnapshot' ); |
342 | 364 | ||
@@ -349,12 +371,12 @@ | |||
349 | range = selection.getRanges()[ 0 ]; | 371 | range = selection.getRanges()[ 0 ]; |
350 | } | 372 | } |
351 | 373 | ||
352 | // Insert element into first range only and ignore the rest (#11183). | 374 | // Insert element into first range only and ignore the rest (http://dev.ckeditor.com/ticket/11183). |
353 | if ( this.insertElementIntoRange( element, range ) ) { | 375 | if ( this.insertElementIntoRange( element, range ) ) { |
354 | range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END ); | 376 | range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END ); |
355 | 377 | ||
356 | // If we're inserting a block element, the new cursor position must be | 378 | // If we're inserting a block element, the new cursor position must be |
357 | // optimized. (#3100,#5436,#8950) | 379 | // optimized. (http://dev.ckeditor.com/ticket/3100,http://dev.ckeditor.com/ticket/5436,http://dev.ckeditor.com/ticket/8950) |
358 | if ( isBlock ) { | 380 | if ( isBlock ) { |
359 | // Find next, meaningful element. | 381 | // Find next, meaningful element. |
360 | var next = element.getNext( function( node ) { | 382 | var next = element.getNext( function( node ) { |
@@ -415,12 +437,19 @@ | |||
415 | // Remove the original contents, merge split nodes. | 437 | // Remove the original contents, merge split nodes. |
416 | range.deleteContents( 1 ); | 438 | range.deleteContents( 1 ); |
417 | 439 | ||
418 | // If range is placed in inermediate element (not td or th), we need to do three things: | 440 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT ) { |
419 | // * fill emptied <td/th>s with if browser needs them, | 441 | // If range is placed in intermediate element (not td or th), we need to do three things: |
420 | // * remove empty text nodes so IE8 won't crash (http://dev.ckeditor.com/ticket/11183#comment:8), | 442 | // * fill emptied <td/th>s with if browser needs them, |
421 | // * fix structure and move range into the <td/th> element. | 443 | // * remove empty text nodes so IE8 won't crash |
422 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.is( { tr: 1, table: 1, tbody: 1, thead: 1, tfoot: 1 } ) ) | 444 | // (http://dev.ckeditor.com/ticket/11183#comment:8), |
423 | fixTableAfterContentsDeletion( range ); | 445 | // * fix structure and move range into the <td/th> element. |
446 | if ( range.startContainer.is( { tr: 1, table: 1, tbody: 1, thead: 1, tfoot: 1 } ) ) { | ||
447 | fixTableAfterContentsDeletion( range ); | ||
448 | } else if ( range.startContainer.is( CKEDITOR.dtd.$list ) ) { | ||
449 | // Similarly there's a need for lists. | ||
450 | fixListAfterContentsDelete( range ); | ||
451 | } | ||
452 | } | ||
424 | 453 | ||
425 | // If we're inserting a block at dtd-violated position, split | 454 | // If we're inserting a block at dtd-violated position, split |
426 | // the parent blocks until we reach blockLimit. | 455 | // the parent blocks until we reach blockLimit. |
@@ -435,7 +464,7 @@ | |||
435 | range.splitElement( current ); | 464 | range.splitElement( current ); |
436 | 465 | ||
437 | // If we're in an empty block which indicate a new paragraph, | 466 | // If we're in an empty block which indicate a new paragraph, |
438 | // simply replace it with the inserting block.(#3664) | 467 | // simply replace it with the inserting block.(http://dev.ckeditor.com/ticket/3664) |
439 | else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { | 468 | else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { |
440 | range.setStartBefore( current ); | 469 | range.setStartBefore( current ); |
441 | range.collapse( true ); | 470 | range.collapse( true ); |
@@ -749,7 +778,7 @@ | |||
749 | range.checkEndOfBlock() && | 778 | range.checkEndOfBlock() && |
750 | path.block && | 779 | path.block && |
751 | !range.root.equals( path.block ) && | 780 | !range.root.equals( path.block ) && |
752 | // Do not remove a block with bookmarks. (#13465) | 781 | // Do not remove a block with bookmarks. (http://dev.ckeditor.com/ticket/13465) |
753 | !hasBookmarks( path.block ) ) { | 782 | !hasBookmarks( path.block ) ) { |
754 | range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START ); | 783 | range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START ); |
755 | path.block.remove(); | 784 | path.block.remove(); |
@@ -811,7 +840,7 @@ | |||
811 | 840 | ||
812 | // IE considers control-type element as separate | 841 | // IE considers control-type element as separate |
813 | // focus host when selected, avoid destroying the | 842 | // focus host when selected, avoid destroying the |
814 | // selection in such case. (#5812) (#8949) | 843 | // selection in such case. (http://dev.ckeditor.com/ticket/5812) (http://dev.ckeditor.com/ticket/8949) |
815 | if ( ieSel && ieSel.type == 'Control' ) | 844 | if ( ieSel && ieSel.type == 'Control' ) |
816 | return; | 845 | return; |
817 | 846 | ||
@@ -862,6 +891,30 @@ | |||
862 | this.hasFocus = true; | 891 | this.hasFocus = true; |
863 | }, null, null, -1 ); | 892 | }, null, null, -1 ); |
864 | 893 | ||
894 | if ( CKEDITOR.env.webkit ) { | ||
895 | // [WebKit] Save scrollTop value so it can be used when restoring locked selection. (http://dev.ckeditor.com/ticket/14659) | ||
896 | this.on( 'scroll', function() { | ||
897 | editor._.previousScrollTop = editor.editable().$.scrollTop; | ||
898 | }, null, null, -1 ); | ||
899 | } | ||
900 | |||
901 | // [Edge] This is the other part of the workaround for Edge which restores saved | ||
902 | // scrollTop value and removes listener which is not needed anymore. (http://dev.ckeditor.com/ticket/14825) | ||
903 | if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) { | ||
904 | |||
905 | var fixScrollOnFocus = function() { | ||
906 | var editable = editor.editable(); | ||
907 | |||
908 | if ( editor._.previousScrollTop != null && editable.getDocument().equals( CKEDITOR.document ) ) { | ||
909 | editable.$.scrollTop = editor._.previousScrollTop; | ||
910 | editor._.previousScrollTop = null; | ||
911 | this.removeListener( 'scroll', fixScrollOnFocus ); | ||
912 | } | ||
913 | }; | ||
914 | |||
915 | this.on( 'scroll', fixScrollOnFocus ); | ||
916 | } | ||
917 | |||
865 | // Register to focus manager. | 918 | // Register to focus manager. |
866 | editor.focusManager.add( this ); | 919 | editor.focusManager.add( this ); |
867 | 920 | ||
@@ -902,12 +955,16 @@ | |||
902 | // Create the content stylesheet for this document. | 955 | // Create the content stylesheet for this document. |
903 | var styles = CKEDITOR.getCss(); | 956 | var styles = CKEDITOR.getCss(); |
904 | if ( styles ) { | 957 | if ( styles ) { |
905 | var head = doc.getHead(); | 958 | var head = doc.getHead(), |
906 | if ( !head.getCustomData( 'stylesheet' ) ) { | 959 | stylesElement = head.getCustomData( 'stylesheet' ); |
960 | |||
961 | if ( !stylesElement ) { | ||
907 | var sheet = doc.appendStyleText( styles ); | 962 | var sheet = doc.appendStyleText( styles ); |
908 | sheet = new CKEDITOR.dom.element( sheet.ownerNode || sheet.owningElement ); | 963 | sheet = new CKEDITOR.dom.element( sheet.ownerNode || sheet.owningElement ); |
909 | head.setCustomData( 'stylesheet', sheet ); | 964 | head.setCustomData( 'stylesheet', sheet ); |
910 | sheet.data( 'cke-temp', 1 ); | 965 | sheet.data( 'cke-temp', 1 ); |
966 | } else if ( styles != stylesElement.getText() ) { | ||
967 | CKEDITOR.env.ie && CKEDITOR.env.version < 9 ? stylesElement.$.styleSheet.cssText = styles : stylesElement.setText( styles ); | ||
911 | } | 968 | } |
912 | } | 969 | } |
913 | 970 | ||
@@ -918,7 +975,7 @@ | |||
918 | // Pass this configuration to styles system. | 975 | // Pass this configuration to styles system. |
919 | this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); | 976 | this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); |
920 | 977 | ||
921 | // Prevent the browser opening read-only links. (#6032 & #10912) | 978 | // Prevent the browser opening read-only links. (http://dev.ckeditor.com/ticket/6032 & http://dev.ckeditor.com/ticket/10912) |
922 | this.attachListener( this, 'click', function( evt ) { | 979 | this.attachListener( this, 'click', function( evt ) { |
923 | evt = evt.data; | 980 | evt = evt.data; |
924 | 981 | ||
@@ -931,7 +988,7 @@ | |||
931 | var backspaceOrDelete = { 8: 1, 46: 1 }; | 988 | var backspaceOrDelete = { 8: 1, 46: 1 }; |
932 | 989 | ||
933 | // Override keystrokes which should have deletion behavior | 990 | // Override keystrokes which should have deletion behavior |
934 | // on fully selected element . (#4047) (#7645) | 991 | // on fully selected element . (http://dev.ckeditor.com/ticket/4047) (http://dev.ckeditor.com/ticket/7645) |
935 | this.attachListener( editor, 'key', function( evt ) { | 992 | this.attachListener( editor, 'key', function( evt ) { |
936 | if ( editor.readOnly ) | 993 | if ( editor.readOnly ) |
937 | return true; | 994 | return true; |
@@ -941,10 +998,15 @@ | |||
941 | var keyCode = evt.data.domEvent.getKey(), | 998 | var keyCode = evt.data.domEvent.getKey(), |
942 | isHandled; | 999 | isHandled; |
943 | 1000 | ||
1001 | // Prevent of reading path of empty range (http://dev.ckeditor.com/ticket/13096, #457). | ||
1002 | var sel = editor.getSelection(); | ||
1003 | if ( sel.getRanges().length === 0 ) { | ||
1004 | return; | ||
1005 | } | ||
1006 | |||
944 | // Backspace OR Delete. | 1007 | // Backspace OR Delete. |
945 | if ( keyCode in backspaceOrDelete ) { | 1008 | if ( keyCode in backspaceOrDelete ) { |
946 | var sel = editor.getSelection(), | 1009 | var selected, |
947 | selected, | ||
948 | range = sel.getRanges()[ 0 ], | 1010 | range = sel.getRanges()[ 0 ], |
949 | path = range.startPath(), | 1011 | path = range.startPath(), |
950 | block, | 1012 | block, |
@@ -952,16 +1014,17 @@ | |||
952 | next, | 1014 | next, |
953 | rtl = keyCode == 8; | 1015 | rtl = keyCode == 8; |
954 | 1016 | ||
1017 | |||
955 | if ( | 1018 | if ( |
956 | // [IE<11] Remove selected image/anchor/etc here to avoid going back in history. (#10055) | 1019 | // [IE<11] Remove selected image/anchor/etc here to avoid going back in history. (http://dev.ckeditor.com/ticket/10055) |
957 | ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) || | 1020 | ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) || |
958 | // Remove the entire list/table on fully selected content. (#7645) | 1021 | // Remove the entire list/table on fully selected content. (http://dev.ckeditor.com/ticket/7645) |
959 | ( selected = getSelectedTableList( sel ) ) ) { | 1022 | ( selected = getSelectedTableList( sel ) ) ) { |
960 | // Make undo snapshot. | 1023 | // Make undo snapshot. |
961 | editor.fire( 'saveSnapshot' ); | 1024 | editor.fire( 'saveSnapshot' ); |
962 | 1025 | ||
963 | // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will | 1026 | // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will |
964 | // break up the selection, safely manage it here. (#4795) | 1027 | // break up the selection, safely manage it here. (http://dev.ckeditor.com/ticket/4795) |
965 | range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); | 1028 | range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); |
966 | // Remove the control manually. | 1029 | // Remove the control manually. |
967 | selected.remove(); | 1030 | selected.remove(); |
@@ -971,7 +1034,7 @@ | |||
971 | 1034 | ||
972 | isHandled = 1; | 1035 | isHandled = 1; |
973 | } else if ( range.collapsed ) { | 1036 | } else if ( range.collapsed ) { |
974 | // Handle the following special cases: (#6217) | 1037 | // Handle the following special cases: (http://dev.ckeditor.com/ticket/6217) |
975 | // 1. Del/Backspace key before/after table; | 1038 | // 1. Del/Backspace key before/after table; |
976 | // 2. Backspace Key after start of table. | 1039 | // 2. Backspace Key after start of table. |
977 | if ( ( block = path.block ) && | 1040 | if ( ( block = path.block ) && |
@@ -1046,28 +1109,28 @@ | |||
1046 | editor.fire( 'doubleclick', data ); | 1109 | editor.fire( 'doubleclick', data ); |
1047 | } ); | 1110 | } ); |
1048 | 1111 | ||
1049 | // Prevent automatic submission in IE #6336 | 1112 | // Prevent automatic submission in IE http://dev.ckeditor.com/ticket/6336 |
1050 | CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick ); | 1113 | CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick ); |
1051 | 1114 | ||
1052 | // Gecko/Webkit need some help when selecting control type elements. (#3448) | 1115 | // Gecko/Webkit need some help when selecting control type elements. (http://dev.ckeditor.com/ticket/3448) |
1053 | // We apply same behavior for IE Edge. (#13386) | 1116 | // We apply same behavior for IE Edge. (http://dev.ckeditor.com/ticket/13386) |
1054 | if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) { | 1117 | if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) { |
1055 | this.attachListener( this, 'mousedown', function( ev ) { | 1118 | this.attachListener( this, 'mousedown', function( ev ) { |
1056 | var control = ev.data.getTarget(); | 1119 | var control = ev.data.getTarget(); |
1057 | // #11727. Note: htmlDP assures that input/textarea/select have contenteditable=false | 1120 | // http://dev.ckeditor.com/ticket/11727. Note: htmlDP assures that input/textarea/select have contenteditable=false |
1058 | // attributes. However, they also have data-cke-editable attribute, so isReadOnly() returns false, | 1121 | // attributes. However, they also have data-cke-editable attribute, so isReadOnly() returns false, |
1059 | // and therefore those elements are correctly selected by this code. | 1122 | // and therefore those elements are correctly selected by this code. |
1060 | if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) { | 1123 | if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) { |
1061 | editor.getSelection().selectElement( control ); | 1124 | editor.getSelection().selectElement( control ); |
1062 | 1125 | ||
1063 | // Prevent focus from stealing from the editable. (#9515) | 1126 | // Prevent focus from stealing from the editable. (http://dev.ckeditor.com/ticket/9515) |
1064 | if ( control.is( 'input', 'textarea', 'select' ) ) | 1127 | if ( control.is( 'input', 'textarea', 'select' ) ) |
1065 | ev.data.preventDefault(); | 1128 | ev.data.preventDefault(); |
1066 | } | 1129 | } |
1067 | } ); | 1130 | } ); |
1068 | } | 1131 | } |
1069 | 1132 | ||
1070 | // For some reason, after click event is done, IE Edge loses focus on the selected element. (#13386) | 1133 | // For some reason, after click event is done, IE Edge loses focus on the selected element. (http://dev.ckeditor.com/ticket/13386) |
1071 | if ( CKEDITOR.env.edge ) { | 1134 | if ( CKEDITOR.env.edge ) { |
1072 | this.attachListener( this, 'mouseup', function( ev ) { | 1135 | this.attachListener( this, 'mouseup', function( ev ) { |
1073 | var selectedElement = ev.data.getTarget(); | 1136 | var selectedElement = ev.data.getTarget(); |
@@ -1078,7 +1141,7 @@ | |||
1078 | } | 1141 | } |
1079 | 1142 | ||
1080 | // Prevent right click from selecting an empty block even | 1143 | // Prevent right click from selecting an empty block even |
1081 | // when selection is anchored inside it. (#5845) | 1144 | // when selection is anchored inside it. (http://dev.ckeditor.com/ticket/5845) |
1082 | if ( CKEDITOR.env.gecko ) { | 1145 | if ( CKEDITOR.env.gecko ) { |
1083 | this.attachListener( this, 'mouseup', function( ev ) { | 1146 | this.attachListener( this, 'mouseup', function( ev ) { |
1084 | if ( ev.data.$.button == 2 ) { | 1147 | if ( ev.data.$.button == 2 ) { |
@@ -1109,7 +1172,7 @@ | |||
1109 | } | 1172 | } |
1110 | 1173 | ||
1111 | // Prevent Webkit/Blink from going rogue when joining | 1174 | // Prevent Webkit/Blink from going rogue when joining |
1112 | // blocks on BACKSPACE/DEL (#11861,#9998). | 1175 | // blocks on BACKSPACE/DEL (http://dev.ckeditor.com/ticket/11861,http://dev.ckeditor.com/ticket/9998). |
1113 | if ( CKEDITOR.env.webkit ) { | 1176 | if ( CKEDITOR.env.webkit ) { |
1114 | this.attachListener( editor, 'key', function( evt ) { | 1177 | this.attachListener( editor, 'key', function( evt ) { |
1115 | if ( editor.readOnly ) { | 1178 | if ( editor.readOnly ) { |
@@ -1123,8 +1186,14 @@ | |||
1123 | if ( !( key in backspaceOrDelete ) ) | 1186 | if ( !( key in backspaceOrDelete ) ) |
1124 | return; | 1187 | return; |
1125 | 1188 | ||
1189 | // Prevent of reading path of empty range (http://dev.ckeditor.com/ticket/13096, #457). | ||
1190 | var sel = editor.getSelection(); | ||
1191 | if ( sel.getRanges().length === 0 ) { | ||
1192 | return; | ||
1193 | } | ||
1194 | |||
1126 | var backspace = key == 8, | 1195 | var backspace = key == 8, |
1127 | range = editor.getSelection().getRanges()[ 0 ], | 1196 | range = sel.getRanges()[ 0 ], |
1128 | startPath = range.startPath(); | 1197 | startPath = range.startPath(); |
1129 | 1198 | ||
1130 | if ( range.collapsed ) { | 1199 | if ( range.collapsed ) { |
@@ -1135,7 +1204,7 @@ | |||
1135 | return; | 1204 | return; |
1136 | } | 1205 | } |
1137 | 1206 | ||
1138 | // Scroll to the new position of the caret (#11960). | 1207 | // Scroll to the new position of the caret (http://dev.ckeditor.com/ticket/11960). |
1139 | editor.getSelection().scrollIntoView(); | 1208 | editor.getSelection().scrollIntoView(); |
1140 | editor.fire( 'saveSnapshot' ); | 1209 | editor.fire( 'saveSnapshot' ); |
1141 | 1210 | ||
@@ -1190,8 +1259,9 @@ | |||
1190 | * | 1259 | * |
1191 | * @method editable | 1260 | * @method editable |
1192 | * @member CKEDITOR.editor | 1261 | * @member CKEDITOR.editor |
1193 | * @param {CKEDITOR.dom.element/CKEDITOR.editable} elementOrEditable The | 1262 | * @param {CKEDITOR.dom.element/CKEDITOR.editable} [elementOrEditable] The |
1194 | * DOM element to become the editable or a {@link CKEDITOR.editable} object. | 1263 | * DOM element to become the editable or a {@link CKEDITOR.editable} object. |
1264 | * @returns {CKEDITOR.dom.element/null} The editor's editable element, or `null` if not available. | ||
1195 | */ | 1265 | */ |
1196 | CKEDITOR.editor.prototype.editable = function( element ) { | 1266 | CKEDITOR.editor.prototype.editable = function( element ) { |
1197 | var editable = this._.editable; | 1267 | var editable = this._.editable; |
@@ -1214,7 +1284,7 @@ | |||
1214 | CKEDITOR.on( 'instanceLoaded', function( evt ) { | 1284 | CKEDITOR.on( 'instanceLoaded', function( evt ) { |
1215 | var editor = evt.editor; | 1285 | var editor = evt.editor; |
1216 | 1286 | ||
1217 | // and flag that the element was locked by our code so it'll be editable by the editor functions (#6046). | 1287 | // and flag that the element was locked by our code so it'll be editable by the editor functions (http://dev.ckeditor.com/ticket/6046). |
1218 | editor.on( 'insertElement', function( evt ) { | 1288 | editor.on( 'insertElement', function( evt ) { |
1219 | var element = evt.data; | 1289 | var element = evt.data; |
1220 | if ( element.type == CKEDITOR.NODE_ELEMENT && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) { | 1290 | if ( element.type == CKEDITOR.NODE_ELEMENT && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) { |
@@ -1229,9 +1299,9 @@ | |||
1229 | if ( editor.readOnly ) | 1299 | if ( editor.readOnly ) |
1230 | return; | 1300 | return; |
1231 | 1301 | ||
1232 | // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189) | 1302 | // Auto fixing on some document structure weakness to enhance usabilities. (http://dev.ckeditor.com/ticket/3190 and http://dev.ckeditor.com/ticket/3189) |
1233 | var sel = editor.getSelection(); | 1303 | var sel = editor.getSelection(); |
1234 | // Do it only when selection is not locked. (#8222) | 1304 | // Do it only when selection is not locked. (http://dev.ckeditor.com/ticket/8222) |
1235 | if ( sel && !sel.isLocked ) { | 1305 | if ( sel && !sel.isLocked ) { |
1236 | var isDirty = editor.checkDirty(); | 1306 | var isDirty = editor.checkDirty(); |
1237 | 1307 | ||
@@ -1281,7 +1351,7 @@ | |||
1281 | } ); | 1351 | } ); |
1282 | } ); | 1352 | } ); |
1283 | 1353 | ||
1284 | // #9222: Show text cursor in Gecko. | 1354 | // http://dev.ckeditor.com/ticket/9222: Show text cursor in Gecko. |
1285 | // Show default cursor over control elements on all non-IEs. | 1355 | // Show default cursor over control elements on all non-IEs. |
1286 | CKEDITOR.addCss( '.cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}' ); | 1356 | CKEDITOR.addCss( '.cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}' ); |
1287 | 1357 | ||
@@ -1291,15 +1361,15 @@ | |||
1291 | // | 1361 | // |
1292 | // | 1362 | // |
1293 | 1363 | ||
1294 | var isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ), | 1364 | isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ), |
1295 | isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ), | 1365 | isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ), |
1296 | isEmpty = CKEDITOR.dom.walker.empty(), | 1366 | isEmpty = CKEDITOR.dom.walker.empty(), |
1297 | isBogus = CKEDITOR.dom.walker.bogus(), | 1367 | isBogus = CKEDITOR.dom.walker.bogus(), |
1298 | // Matching an empty paragraph at the end of document. | 1368 | // Matching an empty paragraph at the end of document. |
1299 | emptyParagraphRegexp = /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi; | 1369 | emptyParagraphRegexp = /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi; |
1300 | 1370 | ||
1301 | // Auto-fixing block-less content by wrapping paragraph (#3190), prevent | 1371 | // Auto-fixing block-less content by wrapping paragraph (http://dev.ckeditor.com/ticket/3190), prevent |
1302 | // non-exitable-block by padding extra br.(#3189) | 1372 | // non-exitable-block by padding extra br.(http://dev.ckeditor.com/ticket/3189) |
1303 | // Returns truly value when dom was changed, falsy otherwise. | 1373 | // Returns truly value when dom was changed, falsy otherwise. |
1304 | function fixDom( evt ) { | 1374 | function fixDom( evt ) { |
1305 | var editor = evt.editor, | 1375 | var editor = evt.editor, |
@@ -1320,7 +1390,7 @@ | |||
1320 | } | 1390 | } |
1321 | 1391 | ||
1322 | // When we're in block enter mode, a new paragraph will be established | 1392 | // When we're in block enter mode, a new paragraph will be established |
1323 | // to encapsulate inline contents inside editable. (#3657) | 1393 | // to encapsulate inline contents inside editable. (http://dev.ckeditor.com/ticket/3657) |
1324 | // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection | 1394 | // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection |
1325 | // inside non-editable content. This happens e.g. if non-editable block is the only | 1395 | // inside non-editable content. This happens e.g. if non-editable block is the only |
1326 | // content of editable. | 1396 | // content of editable. |
@@ -1348,7 +1418,7 @@ | |||
1348 | 1418 | ||
1349 | selectionUpdateNeeded = 1; | 1419 | selectionUpdateNeeded = 1; |
1350 | 1420 | ||
1351 | // Cancel this selection change in favor of the next (correct). (#6811) | 1421 | // Cancel this selection change in favor of the next (correct). (http://dev.ckeditor.com/ticket/6811) |
1352 | evt.cancel(); | 1422 | evt.cancel(); |
1353 | } | 1423 | } |
1354 | } | 1424 | } |
@@ -1364,13 +1434,13 @@ | |||
1364 | if ( selection.isFake ) | 1434 | if ( selection.isFake ) |
1365 | return 0; | 1435 | return 0; |
1366 | 1436 | ||
1367 | // Ensure bogus br could help to move cursor (out of styles) to the end of block. (#7041) | 1437 | // Ensure bogus br could help to move cursor (out of styles) to the end of block. (http://dev.ckeditor.com/ticket/7041) |
1368 | var pathBlock = path.block || path.blockLimit, | 1438 | var pathBlock = path.block || path.blockLimit, |
1369 | lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); | 1439 | lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); |
1370 | 1440 | ||
1371 | // Check some specialities of the current path block: | 1441 | // Check some specialities of the current path block: |
1372 | // 1. It is really displayed as block; (#7221) | 1442 | // 1. It is really displayed as block; (http://dev.ckeditor.com/ticket/7221) |
1373 | // 2. It doesn't end with one inner block; (#7467) | 1443 | // 2. It doesn't end with one inner block; (http://dev.ckeditor.com/ticket/7467) |
1374 | // 3. It doesn't have bogus br yet. | 1444 | // 3. It doesn't have bogus br yet. |
1375 | if ( | 1445 | if ( |
1376 | pathBlock && pathBlock.isBlockBoundary() && | 1446 | pathBlock && pathBlock.isBlockBoundary() && |
@@ -1507,7 +1577,7 @@ | |||
1507 | // Whether in given context (pathBlock, pathBlockLimit and editor settings) | 1577 | // Whether in given context (pathBlock, pathBlockLimit and editor settings) |
1508 | // editor should automatically wrap inline contents with blocks. | 1578 | // editor should automatically wrap inline contents with blocks. |
1509 | function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) { | 1579 | function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) { |
1510 | // Check whether pathBlock equals pathBlockLimit to support nested editable (#12162). | 1580 | // Check whether pathBlock equals pathBlockLimit to support nested editable (http://dev.ckeditor.com/ticket/12162). |
1511 | return editor.config.autoParagraph !== false && | 1581 | return editor.config.autoParagraph !== false && |
1512 | editor.activeEnterMode != CKEDITOR.ENTER_BR && | 1582 | editor.activeEnterMode != CKEDITOR.ENTER_BR && |
1513 | ( | 1583 | ( |
@@ -1523,7 +1593,7 @@ | |||
1523 | // | 1593 | // |
1524 | // Functions related to insertXXX methods | 1594 | // Functions related to insertXXX methods |
1525 | // | 1595 | // |
1526 | var insert = ( function() { | 1596 | insert = ( function() { |
1527 | 'use strict'; | 1597 | 'use strict'; |
1528 | 1598 | ||
1529 | var DTD = CKEDITOR.dtd; | 1599 | var DTD = CKEDITOR.dtd; |
@@ -1574,7 +1644,7 @@ | |||
1574 | 1644 | ||
1575 | // Select range and stop execution. | 1645 | // Select range and stop execution. |
1576 | // If data has been totally emptied after the filtering, | 1646 | // If data has been totally emptied after the filtering, |
1577 | // any insertion is pointless (#10339). | 1647 | // any insertion is pointless (http://dev.ckeditor.com/ticket/10339). |
1578 | if ( data && processDataForInsertion( that, data ) ) { | 1648 | if ( data && processDataForInsertion( that, data ) ) { |
1579 | // DATA INSERTION | 1649 | // DATA INSERTION |
1580 | insertDataIntoRange( that ); | 1650 | insertDataIntoRange( that ); |
@@ -1959,7 +2029,7 @@ | |||
1959 | nodeName = node.getName(); | 2029 | nodeName = node.getName(); |
1960 | 2030 | ||
1961 | // Extract only the list items, when insertion happens | 2031 | // Extract only the list items, when insertion happens |
1962 | // inside of a list, reads as rearrange list items. (#7957) | 2032 | // inside of a list, reads as rearrange list items. (http://dev.ckeditor.com/ticket/7957) |
1963 | if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) { | 2033 | if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) { |
1964 | nodesData = nodesData.concat( extractNodesData( node, that ) ); | 2034 | nodesData = nodesData.concat( extractNodesData( node, that ) ); |
1965 | continue; | 2035 | continue; |
@@ -2207,7 +2277,7 @@ | |||
2207 | } | 2277 | } |
2208 | 2278 | ||
2209 | // Don't use String.replace because it fails in IE7 if special replacement | 2279 | // Don't use String.replace because it fails in IE7 if special replacement |
2210 | // characters ($$, $&, etc.) are in data (#10367). | 2280 | // characters ($$, $&, etc.) are in data (http://dev.ckeditor.com/ticket/10367). |
2211 | return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data ); | 2281 | return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data ); |
2212 | } | 2282 | } |
2213 | 2283 | ||
@@ -2232,7 +2302,7 @@ | |||
2232 | // 1. Fixes a range which is a result of deleteContents() and is placed in an intermediate element (see dtd.$intermediate), | 2302 | // 1. Fixes a range which is a result of deleteContents() and is placed in an intermediate element (see dtd.$intermediate), |
2233 | // inside a table. A goal is to find a closest <td> or <th> element and when this fails, recreate the structure of the table. | 2303 | // inside a table. A goal is to find a closest <td> or <th> element and when this fails, recreate the structure of the table. |
2234 | // 2. Fixes empty cells by appending bogus <br>s or deleting empty text nodes in IE<=8 case. | 2304 | // 2. Fixes empty cells by appending bogus <br>s or deleting empty text nodes in IE<=8 case. |
2235 | var fixTableAfterContentsDeletion = ( function() { | 2305 | fixTableAfterContentsDeletion = ( function() { |
2236 | // Creates an element walker which can only "go deeper". It won't | 2306 | // Creates an element walker which can only "go deeper". It won't |
2237 | // move out from any element. Therefore it can be used to find <td>x</td> in cases like: | 2307 | // move out from any element. Therefore it can be used to find <td>x</td> in cases like: |
2238 | // <table><tbody><tr><td>x</td></tr></tbody>^<tfoot>... | 2308 | // <table><tbody><tr><td>x</td></tr></tbody>^<tfoot>... |
@@ -2330,6 +2400,65 @@ | |||
2330 | }; | 2400 | }; |
2331 | } )(); | 2401 | } )(); |
2332 | 2402 | ||
2403 | fixListAfterContentsDelete = ( function() { | ||
2404 | // Creates an element walker which operates only within lists. | ||
2405 | function getFixListSelectionWalker( testRange ) { | ||
2406 | var walker = new CKEDITOR.dom.walker( testRange ); | ||
2407 | walker.guard = function( node, isMovingOut ) { | ||
2408 | if ( isMovingOut ) | ||
2409 | return false; | ||
2410 | if ( node.type == CKEDITOR.NODE_ELEMENT ) | ||
2411 | return node.is( CKEDITOR.dtd.$list ) || node.is( CKEDITOR.dtd.$listItem ); | ||
2412 | }; | ||
2413 | walker.evaluator = function( node ) { | ||
2414 | return node.type == CKEDITOR.NODE_ELEMENT && node.is( CKEDITOR.dtd.$listItem ); | ||
2415 | }; | ||
2416 | |||
2417 | return walker; | ||
2418 | } | ||
2419 | |||
2420 | return function( range ) { | ||
2421 | var container = range.startContainer, | ||
2422 | appendToStart = false, | ||
2423 | testRange, | ||
2424 | deeperSibling; | ||
2425 | |||
2426 | // Look left. | ||
2427 | testRange = range.clone(); | ||
2428 | testRange.setStart( container, 0 ); | ||
2429 | deeperSibling = getFixListSelectionWalker( testRange ).lastBackward(); | ||
2430 | |||
2431 | // If left is empty, look right. | ||
2432 | if ( !deeperSibling ) { | ||
2433 | testRange = range.clone(); | ||
2434 | testRange.setEndAt( container, CKEDITOR.POSITION_BEFORE_END ); | ||
2435 | deeperSibling = getFixListSelectionWalker( testRange ).lastForward(); | ||
2436 | appendToStart = true; | ||
2437 | } | ||
2438 | |||
2439 | // If there's no deeper nested element in both direction - container is empty - we'll use it then. | ||
2440 | if ( !deeperSibling ) | ||
2441 | deeperSibling = container; | ||
2442 | |||
2443 | // We found a list what means that it's empty - remove it completely. | ||
2444 | if ( deeperSibling.is( CKEDITOR.dtd.$list ) ) { | ||
2445 | range.setStartAt( deeperSibling, CKEDITOR.POSITION_BEFORE_START ); | ||
2446 | range.collapse( true ); | ||
2447 | deeperSibling.remove(); | ||
2448 | return; | ||
2449 | } | ||
2450 | |||
2451 | // To avoid setting selection after bogus, remove it from the target list item. | ||
2452 | // We can safely do that, because we'll insert element into that cell. | ||
2453 | var bogus = deeperSibling.getBogus(); | ||
2454 | if ( bogus ) | ||
2455 | bogus.remove(); | ||
2456 | |||
2457 | range.moveToPosition( deeperSibling, appendToStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END ); | ||
2458 | range.select(); | ||
2459 | }; | ||
2460 | } )(); | ||
2461 | |||
2333 | function mergeBlocksCollapsedSelection( editor, range, backspace, startPath ) { | 2462 | function mergeBlocksCollapsedSelection( editor, range, backspace, startPath ) { |
2334 | var startBlock = startPath.block; | 2463 | var startBlock = startPath.block; |
2335 | 2464 | ||
@@ -2409,7 +2538,7 @@ | |||
2409 | if ( ( bogus = startBlock.getBogus() ) ) | 2538 | if ( ( bogus = startBlock.getBogus() ) ) |
2410 | bogus.remove(); | 2539 | bogus.remove(); |
2411 | 2540 | ||
2412 | // Changing end container to element from text node (#12503). | 2541 | // Changing end container to element from text node (http://dev.ckeditor.com/ticket/12503). |
2413 | range.enlarge( CKEDITOR.ENLARGE_INLINE ); | 2542 | range.enlarge( CKEDITOR.ENLARGE_INLINE ); |
2414 | 2543 | ||
2415 | // Delete range contents. Do NOT merge. Merging is weird. | 2544 | // Delete range contents. Do NOT merge. Merging is weird. |
@@ -2432,7 +2561,7 @@ | |||
2432 | range = editor.getSelection().getRanges()[ 0 ]; | 2561 | range = editor.getSelection().getRanges()[ 0 ]; |
2433 | range.collapse( 1 ); | 2562 | range.collapse( 1 ); |
2434 | 2563 | ||
2435 | // Optimizing range containers from text nodes to elements (#12503). | 2564 | // Optimizing range containers from text nodes to elements (http://dev.ckeditor.com/ticket/12503). |
2436 | range.optimize(); | 2565 | range.optimize(); |
2437 | if ( range.startContainer.getHtml() === '' ) { | 2566 | if ( range.startContainer.getHtml() === '' ) { |
2438 | range.startContainer.appendBogus(); | 2567 | range.startContainer.appendBogus(); |
@@ -2473,7 +2602,7 @@ | |||
2473 | // | 2602 | // |
2474 | // Helpers for editable.getHtmlFromRange. | 2603 | // Helpers for editable.getHtmlFromRange. |
2475 | // | 2604 | // |
2476 | var getHtmlFromRangeHelpers = { | 2605 | getHtmlFromRangeHelpers = { |
2477 | eol: { | 2606 | eol: { |
2478 | detect: function( that, editable ) { | 2607 | detect: function( that, editable ) { |
2479 | var range = that.range, | 2608 | var range = that.range, |
@@ -2638,7 +2767,7 @@ | |||
2638 | // | 2767 | // |
2639 | // Helpers for editable.extractHtmlFromRange. | 2768 | // Helpers for editable.extractHtmlFromRange. |
2640 | // | 2769 | // |
2641 | var extractHtmlFromRangeHelpers = ( function() { | 2770 | extractHtmlFromRangeHelpers = ( function() { |
2642 | function optimizeBookmarkNode( node, toStart ) { | 2771 | function optimizeBookmarkNode( node, toStart ) { |
2643 | var parent = node.getParent(); | 2772 | var parent = node.getParent(); |
2644 | 2773 | ||
@@ -2654,7 +2783,7 @@ | |||
2654 | while ( ( next = endBookmark.getNext() ) ) { | 2783 | while ( ( next = endBookmark.getNext() ) ) { |
2655 | next.insertAfter( startBookmark ); | 2784 | next.insertAfter( startBookmark ); |
2656 | 2785 | ||
2657 | // Update startBookmark after insertion to avoid the reversal of nodes (#13449). | 2786 | // Update startBookmark after insertion to avoid the reversal of nodes (http://dev.ckeditor.com/ticket/13449). |
2658 | startBookmark = next; | 2787 | startBookmark = next; |
2659 | } | 2788 | } |
2660 | 2789 | ||
@@ -2805,7 +2934,7 @@ | |||
2805 | 2934 | ||
2806 | walker.guard = function( node, leaving ) { | 2935 | walker.guard = function( node, leaving ) { |
2807 | // Guard may be executed on some node boundaries multiple times, | 2936 | // Guard may be executed on some node boundaries multiple times, |
2808 | // what results in creating more than one range for each selected cell. (#12964) | 2937 | // what results in creating more than one range for each selected cell. (http://dev.ckeditor.com/ticket/12964) |
2809 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { | 2938 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { |
2810 | var key = 'visited_' + ( leaving ? 'out' : 'in' ); | 2939 | var key = 'visited_' + ( leaving ? 'out' : 'in' ); |
2811 | if ( node.getCustomData( key ) ) { | 2940 | if ( node.getCustomData( key ) ) { |