aboutsummaryrefslogtreecommitdiff
path: root/sources/core/editable.js
diff options
context:
space:
mode:
Diffstat (limited to 'sources/core/editable.js')
-rw-r--r--sources/core/editable.js267
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[^>]*>|&nbsp;|\u00A0|&#160;)?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi; 1369 emptyParagraphRegexp = /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)?\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 ) ) {