diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2017-12-04 18:55:29 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2017-12-04 18:55:29 +0100 |
commit | 1794320dcfdfcd19572fb1676294f9853a6bbc20 (patch) | |
tree | a4c9e978947d6930d50391747382d7f95a5863e3 /sources/core | |
parent | 7183f6a6a21ad9124e70c997e0168459f377a9f2 (diff) | |
download | ludivine-ckeditor-component-master.tar.gz ludivine-ckeditor-component-master.tar.zst ludivine-ckeditor-component-master.zip |
Diffstat (limited to 'sources/core')
33 files changed, 1062 insertions, 389 deletions
diff --git a/sources/core/_bootstrap.js b/sources/core/_bootstrap.js index 0163912..cc7fb38 100644 --- a/sources/core/_bootstrap.js +++ b/sources/core/_bootstrap.js | |||
@@ -8,7 +8,7 @@ | |||
8 | */ | 8 | */ |
9 | 9 | ||
10 | ( function() { | 10 | ( function() { |
11 | // Disable HC detection in WebKit. (#5429) | 11 | // Disable HC detection in WebKit. (http://dev.ckeditor.com/ticket/5429) |
12 | if ( CKEDITOR.env.webkit ) | 12 | if ( CKEDITOR.env.webkit ) |
13 | CKEDITOR.env.hc = false; | 13 | CKEDITOR.env.hc = false; |
14 | else { | 14 | else { |
@@ -19,13 +19,13 @@ | |||
19 | hcDetect.appendTo( CKEDITOR.document.getHead() ); | 19 | hcDetect.appendTo( CKEDITOR.document.getHead() ); |
20 | 20 | ||
21 | // Update CKEDITOR.env. | 21 | // Update CKEDITOR.env. |
22 | // Catch exception needed sometimes for FF. (#4230) | 22 | // Catch exception needed sometimes for FF. (http://dev.ckeditor.com/ticket/4230) |
23 | try { | 23 | try { |
24 | var top = hcDetect.getComputedStyle( 'border-top-color' ), | 24 | var top = hcDetect.getComputedStyle( 'border-top-color' ), |
25 | right = hcDetect.getComputedStyle( 'border-right-color' ); | 25 | right = hcDetect.getComputedStyle( 'border-right-color' ); |
26 | 26 | ||
27 | // We need to check if getComputedStyle returned any value, because on FF | 27 | // We need to check if getComputedStyle returned any value, because on FF |
28 | // it returnes empty string if CKEditor is loaded in hidden iframe. (#11121) | 28 | // it returnes empty string if CKEditor is loaded in hidden iframe. (http://dev.ckeditor.com/ticket/11121) |
29 | CKEDITOR.env.hc = !!( top && top == right ); | 29 | CKEDITOR.env.hc = !!( top && top == right ); |
30 | } catch ( e ) { | 30 | } catch ( e ) { |
31 | CKEDITOR.env.hc = false; | 31 | CKEDITOR.env.hc = false; |
diff --git a/sources/core/ckeditor.js b/sources/core/ckeditor.js index 2756204..95dd67a 100644 --- a/sources/core/ckeditor.js +++ b/sources/core/ckeditor.js | |||
@@ -126,7 +126,7 @@ CKEDITOR.remove = function( editor ) { | |||
126 | }; | 126 | }; |
127 | 127 | ||
128 | /** | 128 | /** |
129 | * Returns a string will all CSS rules passed to the {@link CKEDITOR#addCss} method. | 129 | * Returns a string with all CSS rules passed to the {@link CKEDITOR#addCss} method. |
130 | * | 130 | * |
131 | * @returns {String} A string containing CSS rules. | 131 | * @returns {String} A string containing CSS rules. |
132 | */ | 132 | */ |
diff --git a/sources/core/command.js b/sources/core/command.js index 0446d24..128cea4 100644 --- a/sources/core/command.js +++ b/sources/core/command.js | |||
@@ -47,7 +47,7 @@ CKEDITOR.command = function( editor, commandDefinition ) { | |||
47 | if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() ) | 47 | if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() ) |
48 | return false; | 48 | return false; |
49 | 49 | ||
50 | if ( this.editorFocus ) // Give editor focus if necessary (#4355). | 50 | if ( this.editorFocus ) // Give editor focus if necessary (http://dev.ckeditor.com/ticket/4355). |
51 | editor.focus(); | 51 | editor.focus(); |
52 | 52 | ||
53 | if ( this.fire( 'exec' ) === false ) | 53 | if ( this.fire( 'exec' ) === false ) |
diff --git a/sources/core/creators/themedui.js b/sources/core/creators/themedui.js index 04927d0..4e7a93e 100644 --- a/sources/core/creators/themedui.js +++ b/sources/core/creators/themedui.js | |||
@@ -280,10 +280,10 @@ CKEDITOR.replaceClass = 'ckeditor'; | |||
280 | outer = container; | 280 | outer = container; |
281 | } | 281 | } |
282 | 282 | ||
283 | // Set as border box width. (#5353) | 283 | // Set as border box width. (http://dev.ckeditor.com/ticket/5353) |
284 | outer.setSize( 'width', width, true ); | 284 | outer.setSize( 'width', width, true ); |
285 | 285 | ||
286 | // WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (#8348) | 286 | // WebKit needs to refresh the iframe size to avoid rendering issues. (1/2) (http://dev.ckeditor.com/ticket/8348) |
287 | contentsFrame && ( contentsFrame.style.width = '1%' ); | 287 | contentsFrame && ( contentsFrame.style.width = '1%' ); |
288 | 288 | ||
289 | // Get the height delta between the outer table and the content area. | 289 | // Get the height delta between the outer table and the content area. |
@@ -295,7 +295,7 @@ CKEDITOR.replaceClass = 'ckeditor'; | |||
295 | 295 | ||
296 | contents.setStyle( 'height', resultContentsHeight + 'px' ); | 296 | contents.setStyle( 'height', resultContentsHeight + 'px' ); |
297 | 297 | ||
298 | // WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (#8348) | 298 | // WebKit needs to refresh the iframe size to avoid rendering issues. (2/2) (http://dev.ckeditor.com/ticket/8348) |
299 | contentsFrame && ( contentsFrame.style.width = '100%' ); | 299 | contentsFrame && ( contentsFrame.style.width = '100%' ); |
300 | 300 | ||
301 | // Emit a resize event. | 301 | // Emit a resize event. |
@@ -337,7 +337,7 @@ CKEDITOR.replaceClass = 'ckeditor'; | |||
337 | // replacement will be done later in the editor creation lifecycle. | 337 | // replacement will be done later in the editor creation lifecycle. |
338 | element.setStyle( 'visibility', 'hidden' ); | 338 | element.setStyle( 'visibility', 'hidden' ); |
339 | 339 | ||
340 | // #8031 Remember if textarea was required and remove the attribute. | 340 | // http://dev.ckeditor.com/ticket/8031 Remember if textarea was required and remove the attribute. |
341 | editor._.required = element.hasAttribute( 'required' ); | 341 | editor._.required = element.hasAttribute( 'required' ); |
342 | element.removeAttribute( 'required' ); | 342 | element.removeAttribute( 'required' ); |
343 | } | 343 | } |
@@ -422,7 +422,7 @@ CKEDITOR.replaceClass = 'ckeditor'; | |||
422 | topHtml: topHtml ? '<span id="' + editor.ui.spaceId( 'top' ) + '" class="cke_top cke_reset_all" role="presentation" style="height:auto">' + topHtml + '</span>' : '', | 422 | topHtml: topHtml ? '<span id="' + editor.ui.spaceId( 'top' ) + '" class="cke_top cke_reset_all" role="presentation" style="height:auto">' + topHtml + '</span>' : '', |
423 | contentId: editor.ui.spaceId( 'contents' ), | 423 | contentId: editor.ui.spaceId( 'contents' ), |
424 | bottomHtml: bottomHtml ? '<span id="' + editor.ui.spaceId( 'bottom' ) + '" class="cke_bottom cke_reset_all" role="presentation">' + bottomHtml + '</span>' : '', | 424 | bottomHtml: bottomHtml ? '<span id="' + editor.ui.spaceId( 'bottom' ) + '" class="cke_bottom cke_reset_all" role="presentation">' + bottomHtml + '</span>' : '', |
425 | outerEl: CKEDITOR.env.ie ? 'span' : 'div' // #9571 | 425 | outerEl: CKEDITOR.env.ie ? 'span' : 'div' // http://dev.ckeditor.com/ticket/9571 |
426 | } ) ); | 426 | } ) ); |
427 | 427 | ||
428 | if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) { | 428 | if ( elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) { |
@@ -451,7 +451,7 @@ CKEDITOR.replaceClass = 'ckeditor'; | |||
451 | // Disable browser context menu for editor's chrome. | 451 | // Disable browser context menu for editor's chrome. |
452 | container.disableContextMenu(); | 452 | container.disableContextMenu(); |
453 | 453 | ||
454 | // Redirect the focus into editor for webkit. (#5713) | 454 | // Redirect the focus into editor for webkit. (http://dev.ckeditor.com/ticket/5713) |
455 | CKEDITOR.env.webkit && container.on( 'focus', function() { | 455 | CKEDITOR.env.webkit && container.on( 'focus', function() { |
456 | editor.focus(); | 456 | editor.focus(); |
457 | } ); | 457 | } ); |
diff --git a/sources/core/dom/document.js b/sources/core/dom/document.js index 51c5b19..ebf0bab 100644 --- a/sources/core/dom/document.js +++ b/sources/core/dom/document.js | |||
@@ -262,7 +262,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.document.prototype, { | |||
262 | * @param {String} html The HTML defining the document content. | 262 | * @param {String} html The HTML defining the document content. |
263 | */ | 263 | */ |
264 | write: function( html ) { | 264 | write: function( html ) { |
265 | // Don't leave any history log in IE. (#5657) | 265 | // Don't leave any history log in IE. (http://dev.ckeditor.com/ticket/5657) |
266 | this.$.open( 'text/html', 'replace' ); | 266 | this.$.open( 'text/html', 'replace' ); |
267 | 267 | ||
268 | // Support for custom document.domain in IE. | 268 | // Support for custom document.domain in IE. |
diff --git a/sources/core/dom/domobject.js b/sources/core/dom/domobject.js index 4c593ff..f4e258a 100644 --- a/sources/core/dom/domobject.js +++ b/sources/core/dom/domobject.js | |||
@@ -41,7 +41,7 @@ CKEDITOR.dom.domObject.prototype = ( function() { | |||
41 | return function( domEvent ) { | 41 | return function( domEvent ) { |
42 | // In FF, when reloading the page with the editor focused, it may | 42 | // In FF, when reloading the page with the editor focused, it may |
43 | // throw an error because the CKEDITOR global is not anymore | 43 | // throw an error because the CKEDITOR global is not anymore |
44 | // available. So, we check it here first. (#2923) | 44 | // available. So, we check it here first. (http://dev.ckeditor.com/ticket/2923) |
45 | if ( typeof CKEDITOR != 'undefined' ) | 45 | if ( typeof CKEDITOR != 'undefined' ) |
46 | domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) ); | 46 | domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) ); |
47 | }; | 47 | }; |
@@ -138,7 +138,7 @@ CKEDITOR.dom.domObject.prototype = ( function() { | |||
138 | } | 138 | } |
139 | 139 | ||
140 | // Remove events from events object so fire() method will not call | 140 | // Remove events from events object so fire() method will not call |
141 | // listeners (#11400). | 141 | // listeners (http://dev.ckeditor.com/ticket/11400). |
142 | CKEDITOR.event.prototype.removeAllListeners.call( this ); | 142 | CKEDITOR.event.prototype.removeAllListeners.call( this ); |
143 | } | 143 | } |
144 | }; | 144 | }; |
diff --git a/sources/core/dom/element.js b/sources/core/dom/element.js index e02ff17..31451f9 100644 --- a/sources/core/dom/element.js +++ b/sources/core/dom/element.js | |||
@@ -308,7 +308,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
308 | */ | 308 | */ |
309 | appendText: function( text ) { | 309 | appendText: function( text ) { |
310 | // On IE8 it is impossible to append node to script tag, so we use its text. | 310 | // On IE8 it is impossible to append node to script tag, so we use its text. |
311 | // On the contrary, on Safari the text property is unpredictable in links. (#13232) | 311 | // On the contrary, on Safari the text property is unpredictable in links. (http://dev.ckeditor.com/ticket/13232) |
312 | if ( this.$.text != null && CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) | 312 | if ( this.$.text != null && CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) |
313 | this.$.text += text; | 313 | this.$.text += text; |
314 | else | 314 | else |
@@ -377,7 +377,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
377 | 377 | ||
378 | // In case of Internet Explorer, we must check if there is no background-color | 378 | // In case of Internet Explorer, we must check if there is no background-color |
379 | // added to the element. In such case, we have to overwrite it to prevent "switching it off" | 379 | // added to the element. In such case, we have to overwrite it to prevent "switching it off" |
380 | // by a browser (#14667). | 380 | // by a browser (http://dev.ckeditor.com/ticket/14667). |
381 | if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { | 381 | if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { |
382 | tmpElement = new CKEDITOR.dom.element( 'div' ); | 382 | tmpElement = new CKEDITOR.dom.element( 'div' ); |
383 | 383 | ||
@@ -452,7 +452,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
452 | */ | 452 | */ |
453 | getHtml: function() { | 453 | getHtml: function() { |
454 | var retval = this.$.innerHTML; | 454 | var retval = this.$.innerHTML; |
455 | // Strip <?xml:namespace> tags in IE. (#3341). | 455 | // Strip <?xml:namespace> tags in IE. (http://dev.ckeditor.com/ticket/3341). |
456 | return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval; | 456 | return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval; |
457 | }, | 457 | }, |
458 | 458 | ||
@@ -467,7 +467,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
467 | getOuterHtml: function() { | 467 | getOuterHtml: function() { |
468 | if ( this.$.outerHTML ) { | 468 | if ( this.$.outerHTML ) { |
469 | // IE includes the <?xml:namespace> tag in the outerHTML of | 469 | // IE includes the <?xml:namespace> tag in the outerHTML of |
470 | // namespaced element. So, we must strip it here. (#3341) | 470 | // namespaced element. So, we must strip it here. (http://dev.ckeditor.com/ticket/3341) |
471 | return this.$.outerHTML.replace( /<\?[^>]*>/, '' ); | 471 | return this.$.outerHTML.replace( /<\?[^>]*>/, '' ); |
472 | } | 472 | } |
473 | 473 | ||
@@ -618,7 +618,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
618 | return this.$[ name ]; | 618 | return this.$[ name ]; |
619 | 619 | ||
620 | case 'style': | 620 | case 'style': |
621 | // IE does not return inline styles via getAttribute(). See #2947. | 621 | // IE does not return inline styles via getAttribute(). See http://dev.ckeditor.com/ticket/2947. |
622 | return this.$.style.cssText; | 622 | return this.$.style.cssText; |
623 | 623 | ||
624 | case 'contenteditable': | 624 | case 'contenteditable': |
@@ -679,7 +679,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
679 | function( propertyName ) { | 679 | function( propertyName ) { |
680 | var style = this.getWindow().$.getComputedStyle( this.$, null ); | 680 | var style = this.getWindow().$.getComputedStyle( this.$, null ); |
681 | 681 | ||
682 | // Firefox may return null if we call the above on a hidden iframe. (#9117) | 682 | // Firefox may return null if we call the above on a hidden iframe. (http://dev.ckeditor.com/ticket/9117) |
683 | return style ? style.getPropertyValue( propertyName ) : ''; | 683 | return style ? style.getPropertyValue( propertyName ) : ''; |
684 | } : function( propertyName ) { | 684 | } : function( propertyName ) { |
685 | return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ]; | 685 | return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ]; |
@@ -972,7 +972,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
972 | elementWindow, elementWindowFrame; | 972 | elementWindow, elementWindowFrame; |
973 | 973 | ||
974 | // Webkit and Opera report non-zero offsetHeight despite that | 974 | // Webkit and Opera report non-zero offsetHeight despite that |
975 | // element is inside an invisible iframe. (#4542) | 975 | // element is inside an invisible iframe. (http://dev.ckeditor.com/ticket/4542) |
976 | if ( isVisible && CKEDITOR.env.webkit ) { | 976 | if ( isVisible && CKEDITOR.env.webkit ) { |
977 | elementWindow = this.getWindow(); | 977 | elementWindow = this.getWindow(); |
978 | 978 | ||
@@ -1033,7 +1033,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1033 | // attribute, which will be marked as "specified", even if the | 1033 | // attribute, which will be marked as "specified", even if the |
1034 | // outerHTML of the element is not displaying the class attribute. | 1034 | // outerHTML of the element is not displaying the class attribute. |
1035 | // Note : I was not able to reproduce it outside the editor, | 1035 | // Note : I was not able to reproduce it outside the editor, |
1036 | // but I've faced it while working on the TC of #1391. | 1036 | // but I've faced it while working on the TC of http://dev.ckeditor.com/ticket/1391. |
1037 | if ( this.getAttribute( 'class' ) ) { | 1037 | if ( this.getAttribute( 'class' ) ) { |
1038 | return true; | 1038 | return true; |
1039 | } | 1039 | } |
@@ -1057,7 +1057,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1057 | var attrs = this.$.attributes, | 1057 | var attrs = this.$.attributes, |
1058 | attrsNum = attrs.length; | 1058 | attrsNum = attrs.length; |
1059 | 1059 | ||
1060 | // The _moz_dirty attribute might get into the element after pasting (#5455) | 1060 | // The _moz_dirty attribute might get into the element after pasting (http://dev.ckeditor.com/ticket/5455) |
1061 | var execludeAttrs = { 'data-cke-expando': 1, _moz_dirty: 1 }; | 1061 | var execludeAttrs = { 'data-cke-expando': 1, _moz_dirty: 1 }; |
1062 | 1062 | ||
1063 | return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) ); | 1063 | return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) ); |
@@ -1164,7 +1164,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1164 | function mergeElements( element, sibling, isNext ) { | 1164 | function mergeElements( element, sibling, isNext ) { |
1165 | if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) { | 1165 | if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) { |
1166 | // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>, | 1166 | // Jumping over bookmark nodes and empty inline elements, e.g. <b><i></i></b>, |
1167 | // queuing them to be moved later. (#5567) | 1167 | // queuing them to be moved later. (http://dev.ckeditor.com/ticket/5567) |
1168 | var pendingNodes = []; | 1168 | var pendingNodes = []; |
1169 | 1169 | ||
1170 | while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) { | 1170 | while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) { |
@@ -1194,7 +1194,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1194 | } | 1194 | } |
1195 | 1195 | ||
1196 | return function( inlineOnly ) { | 1196 | return function( inlineOnly ) { |
1197 | // Merge empty links and anchors also. (#5567) | 1197 | // Merge empty links and anchors also. (http://dev.ckeditor.com/ticket/5567) |
1198 | if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) { | 1198 | if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) { |
1199 | return; | 1199 | return; |
1200 | } | 1200 | } |
@@ -1253,7 +1253,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1253 | }; | 1253 | }; |
1254 | } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) { | 1254 | } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) { |
1255 | return function( name, value ) { | 1255 | return function( name, value ) { |
1256 | // IE8 throws error when setting src attribute to non-ssl value. (#7847) | 1256 | // IE8 throws error when setting src attribute to non-ssl value. (http://dev.ckeditor.com/ticket/7847) |
1257 | if ( name == 'src' && value.match( /^http:\/\// ) ) { | 1257 | if ( name == 'src' && value.match( /^http:\/\// ) ) { |
1258 | try { | 1258 | try { |
1259 | standard.apply( this, arguments ); | 1259 | standard.apply( this, arguments ); |
@@ -1501,7 +1501,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1501 | clientLeft = $docElem.clientLeft || body.$.clientLeft || 0, | 1501 | clientLeft = $docElem.clientLeft || body.$.clientLeft || 0, |
1502 | needAdjustScrollAndBorders = true; | 1502 | needAdjustScrollAndBorders = true; |
1503 | 1503 | ||
1504 | // #3804: getBoundingClientRect() works differently on IE and non-IE | 1504 | // http://dev.ckeditor.com/ticket/3804: getBoundingClientRect() works differently on IE and non-IE |
1505 | // browsers, regarding scroll positions. | 1505 | // browsers, regarding scroll positions. |
1506 | // | 1506 | // |
1507 | // On IE, the top position of the <html> element is always 0, no matter | 1507 | // On IE, the top position of the <html> element is always 0, no matter |
@@ -1516,12 +1516,12 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1516 | needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem ); | 1516 | needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem ); |
1517 | } | 1517 | } |
1518 | 1518 | ||
1519 | // #12747. | 1519 | // http://dev.ckeditor.com/ticket/12747. |
1520 | if ( needAdjustScrollAndBorders ) { | 1520 | if ( needAdjustScrollAndBorders ) { |
1521 | var scrollRelativeLeft, | 1521 | var scrollRelativeLeft, |
1522 | scrollRelativeTop; | 1522 | scrollRelativeTop; |
1523 | 1523 | ||
1524 | // See #12758 to know more about document.(documentElement|body).scroll(Left|Top) in Webkit. | 1524 | // See http://dev.ckeditor.com/ticket/12758 to know more about document.(documentElement|body).scroll(Left|Top) in Webkit. |
1525 | if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version >= 12 ) ) { | 1525 | if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version >= 12 ) ) { |
1526 | scrollRelativeLeft = body.$.scrollLeft || $docElem.scrollLeft; | 1526 | scrollRelativeLeft = body.$.scrollLeft || $docElem.scrollLeft; |
1527 | scrollRelativeTop = body.$.scrollTop || $docElem.scrollTop; | 1527 | scrollRelativeTop = body.$.scrollTop || $docElem.scrollTop; |
@@ -1603,7 +1603,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1603 | parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight; | 1603 | parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight; |
1604 | 1604 | ||
1605 | // Skip body element, which will report wrong clientHeight when containing | 1605 | // Skip body element, which will report wrong clientHeight when containing |
1606 | // floated content. (#9523) | 1606 | // floated content. (http://dev.ckeditor.com/ticket/9523) |
1607 | if ( overflowed && !parent.is( 'body' ) ) | 1607 | if ( overflowed && !parent.is( 'body' ) ) |
1608 | this.scrollIntoParent( parent, alignToTop, 1 ); | 1608 | this.scrollIntoParent( parent, alignToTop, 1 ); |
1609 | 1609 | ||
@@ -1677,7 +1677,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1677 | } | 1677 | } |
1678 | 1678 | ||
1679 | // [WebKit] Reset stored scrollTop value to not break scrollIntoView() method flow. | 1679 | // [WebKit] Reset stored scrollTop value to not break scrollIntoView() method flow. |
1680 | // Scrolling breaks when range.select() is used right after element.scrollIntoView(). (#14659) | 1680 | // Scrolling breaks when range.select() is used right after element.scrollIntoView(). (http://dev.ckeditor.com/ticket/14659) |
1681 | if ( CKEDITOR.env.webkit ) { | 1681 | if ( CKEDITOR.env.webkit ) { |
1682 | var editor = this.getEditor( false ); | 1682 | var editor = this.getEditor( false ); |
1683 | 1683 | ||
@@ -1857,7 +1857,7 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab | |||
1857 | this.getParent( true ) && this.$.parentNode.replaceChild( newNode.$, this.$ ); | 1857 | this.getParent( true ) && this.$.parentNode.replaceChild( newNode.$, this.$ ); |
1858 | newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ]; | 1858 | newNode.$[ 'data-cke-expando' ] = this.$[ 'data-cke-expando' ]; |
1859 | this.$ = newNode.$; | 1859 | this.$ = newNode.$; |
1860 | // Bust getName's cache. (#8663) | 1860 | // Bust getName's cache. (http://dev.ckeditor.com/ticket/8663) |
1861 | delete this.getName; | 1861 | delete this.getName; |
1862 | }, | 1862 | }, |
1863 | 1863 | ||
diff --git a/sources/core/dom/elementpath.js b/sources/core/dom/elementpath.js index 1a3aed0..dd50f10 100644 --- a/sources/core/dom/elementpath.js +++ b/sources/core/dom/elementpath.js | |||
@@ -57,6 +57,11 @@ | |||
57 | // Backward compact. | 57 | // Backward compact. |
58 | root = root || startNode.getDocument().getBody(); | 58 | root = root || startNode.getDocument().getBody(); |
59 | 59 | ||
60 | // Assign root value if startNode is null (#424)(https://dev.ckeditor.com/ticket/17028). | ||
61 | if ( !e ) { | ||
62 | e = root; | ||
63 | } | ||
64 | |||
60 | do { | 65 | do { |
61 | if ( e.type == CKEDITOR.NODE_ELEMENT ) { | 66 | if ( e.type == CKEDITOR.NODE_ELEMENT ) { |
62 | elements.push( e ); | 67 | elements.push( e ); |
@@ -84,7 +89,7 @@ | |||
84 | block = e; | 89 | block = e; |
85 | 90 | ||
86 | if ( pathBlockLimitElements[ elementName ] ) { | 91 | if ( pathBlockLimitElements[ elementName ] ) { |
87 | // End level DIV is considered as the block, if no block is available. (#525) | 92 | // End level DIV is considered as the block, if no block is available. (http://dev.ckeditor.com/ticket/525) |
88 | // But it must NOT be the root element (checked above). | 93 | // But it must NOT be the root element (checked above). |
89 | if ( !block && elementName == 'div' && !checkHasBlock( e ) ) | 94 | if ( !block && elementName == 'div' && !checkHasBlock( e ) ) |
90 | block = e; | 95 | block = e; |
@@ -181,7 +186,9 @@ CKEDITOR.dom.elementPath.prototype = { | |||
181 | * @returns {CKEDITOR.dom.element} The first matched dom element or `null`. | 186 | * @returns {CKEDITOR.dom.element} The first matched dom element or `null`. |
182 | */ | 187 | */ |
183 | contains: function( query, excludeRoot, fromTop ) { | 188 | contains: function( query, excludeRoot, fromTop ) { |
184 | var evaluator; | 189 | var i = 0, |
190 | evaluator; | ||
191 | |||
185 | if ( typeof query == 'string' ) | 192 | if ( typeof query == 'string' ) |
186 | evaluator = function( node ) { | 193 | evaluator = function( node ) { |
187 | return node.getName() == query; | 194 | return node.getName() == query; |
@@ -203,14 +210,21 @@ CKEDITOR.dom.elementPath.prototype = { | |||
203 | 210 | ||
204 | var elements = this.elements, | 211 | var elements = this.elements, |
205 | length = elements.length; | 212 | length = elements.length; |
206 | excludeRoot && length--; | 213 | |
214 | if ( excludeRoot ) { | ||
215 | if ( !fromTop ) { | ||
216 | length -= 1; | ||
217 | } else { | ||
218 | i += 1; | ||
219 | } | ||
220 | } | ||
207 | 221 | ||
208 | if ( fromTop ) { | 222 | if ( fromTop ) { |
209 | elements = Array.prototype.slice.call( elements, 0 ); | 223 | elements = Array.prototype.slice.call( elements, 0 ); |
210 | elements.reverse(); | 224 | elements.reverse(); |
211 | } | 225 | } |
212 | 226 | ||
213 | for ( var i = 0; i < length; i++ ) { | 227 | for ( ; i < length; i++ ) { |
214 | if ( evaluator( elements[ i ] ) ) | 228 | if ( evaluator( elements[ i ] ) ) |
215 | return elements[ i ]; | 229 | return elements[ i ]; |
216 | } | 230 | } |
diff --git a/sources/core/dom/iterator.js b/sources/core/dom/iterator.js index 50056ec..9176e33 100644 --- a/sources/core/dom/iterator.js +++ b/sources/core/dom/iterator.js | |||
@@ -45,7 +45,7 @@ | |||
45 | */ | 45 | */ |
46 | this.forceBrBreak = 0; | 46 | this.forceBrBreak = 0; |
47 | 47 | ||
48 | // (#3730). | 48 | // (http://dev.ckeditor.com/ticket/3730). |
49 | /** | 49 | /** |
50 | * Whether to include `<br>` elements in the enlarged range. Should be | 50 | * Whether to include `<br>` elements in the enlarged range. Should be |
51 | * set to `false` when using the iterator in the {@link CKEDITOR#ENTER_BR} mode. | 51 | * set to `false` when using the iterator in the {@link CKEDITOR#ENTER_BR} mode. |
@@ -85,7 +85,7 @@ | |||
85 | */ | 85 | */ |
86 | 86 | ||
87 | var beginWhitespaceRegex = /^[\r\n\t ]+$/, | 87 | var beginWhitespaceRegex = /^[\r\n\t ]+$/, |
88 | // Ignore bookmark nodes.(#3783) | 88 | // Ignore bookmark nodes.(http://dev.ckeditor.com/ticket/3783) |
89 | bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ), | 89 | bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ), |
90 | whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ), | 90 | whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ), |
91 | skipGuard = function( node ) { | 91 | skipGuard = function( node ) { |
@@ -191,12 +191,12 @@ | |||
191 | } | 191 | } |
192 | 192 | ||
193 | // The range must finish right before the boundary, | 193 | // The range must finish right before the boundary, |
194 | // including possibly skipped empty spaces. (#1603) | 194 | // including possibly skipped empty spaces. (http://dev.ckeditor.com/ticket/1603) |
195 | if ( range ) { | 195 | if ( range ) { |
196 | range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); | 196 | range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); |
197 | 197 | ||
198 | // The found boundary must be set as the next one at this | 198 | // The found boundary must be set as the next one at this |
199 | // point. (#1717) | 199 | // point. (http://dev.ckeditor.com/ticket/1717) |
200 | if ( nodeName != 'br' ) { | 200 | if ( nodeName != 'br' ) { |
201 | this._.nextNode = currentNode; | 201 | this._.nextNode = currentNode; |
202 | } | 202 | } |
@@ -244,7 +244,7 @@ | |||
244 | closeRange = 1; | 244 | closeRange = 1; |
245 | includeNode = 0; | 245 | includeNode = 0; |
246 | isLast = isLast || ( parentNode.equals( lastNode ) ); | 246 | isLast = isLast || ( parentNode.equals( lastNode ) ); |
247 | // Make sure range includes bookmarks at the end of the block. (#7359) | 247 | // Make sure range includes bookmarks at the end of the block. (http://dev.ckeditor.com/ticket/7359) |
248 | range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END ); | 248 | range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END ); |
249 | break; | 249 | break; |
250 | } | 250 | } |
@@ -377,7 +377,7 @@ | |||
377 | // Here we are checking in guard function whether current element | 377 | // Here we are checking in guard function whether current element |
378 | // reach lastNode(default behaviour) and root node to prevent against | 378 | // reach lastNode(default behaviour) and root node to prevent against |
379 | // getting out of editor instance root DOM object. | 379 | // getting out of editor instance root DOM object. |
380 | // #12484 | 380 | // http://dev.ckeditor.com/ticket/12484 |
381 | function guardFunction( node ) { | 381 | function guardFunction( node ) { |
382 | return !( node.equals( lastNode ) || node.equals( rootNode ) ); | 382 | return !( node.equals( lastNode ) || node.equals( rootNode ) ); |
383 | } | 383 | } |
@@ -397,7 +397,7 @@ | |||
397 | // Indicate at least one of the range boundaries is inside a preformat block. | 397 | // Indicate at least one of the range boundaries is inside a preformat block. |
398 | touchPre, | 398 | touchPre, |
399 | 399 | ||
400 | // (#12178) | 400 | // (http://dev.ckeditor.com/ticket/12178) |
401 | // Remember if following situation takes place: | 401 | // Remember if following situation takes place: |
402 | // * startAtInnerBoundary: <p>foo[</p>... | 402 | // * startAtInnerBoundary: <p>foo[</p>... |
403 | // * endAtInnerBoundary: ...<p>]bar</p> | 403 | // * endAtInnerBoundary: ...<p>]bar</p> |
@@ -405,13 +405,13 @@ | |||
405 | // Note that we test only if path block exist, because we must properly shrink | 405 | // Note that we test only if path block exist, because we must properly shrink |
406 | // range containing table and/or table cells. | 406 | // range containing table and/or table cells. |
407 | // Note: When range is collapsed there's no way it can be shrinked. | 407 | // Note: When range is collapsed there's no way it can be shrinked. |
408 | // By checking if range is collapsed we also prevent #12308. | 408 | // By checking if range is collapsed we also prevent http://dev.ckeditor.com/ticket/12308. |
409 | startPath = range.startPath(), | 409 | startPath = range.startPath(), |
410 | endPath = range.endPath(), | 410 | endPath = range.endPath(), |
411 | startAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, startPath.block ), | 411 | startAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, startPath.block ), |
412 | endAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, endPath.block, 1 ); | 412 | endAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, endPath.block, 1 ); |
413 | 413 | ||
414 | // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435). | 414 | // Shrink the range to exclude harmful "noises" (http://dev.ckeditor.com/ticket/4087, http://dev.ckeditor.com/ticket/4450, http://dev.ckeditor.com/ticket/5435). |
415 | range.shrink( CKEDITOR.SHRINK_ELEMENT, true ); | 415 | range.shrink( CKEDITOR.SHRINK_ELEMENT, true ); |
416 | 416 | ||
417 | if ( startAtInnerBoundary ) | 417 | if ( startAtInnerBoundary ) |
@@ -437,7 +437,7 @@ | |||
437 | 437 | ||
438 | // We may have an empty text node at the end of block due to [3770]. | 438 | // We may have an empty text node at the end of block due to [3770]. |
439 | // If that node is the lastNode, it would cause our logic to leak to the | 439 | // If that node is the lastNode, it would cause our logic to leak to the |
440 | // next block.(#3887) | 440 | // next block.(http://dev.ckeditor.com/ticket/3887) |
441 | if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) { | 441 | if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) { |
442 | var testRange = this.range.clone(); | 442 | var testRange = this.range.clone(); |
443 | testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END ); | 443 | testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END ); |
diff --git a/sources/core/dom/node.js b/sources/core/dom/node.js index 51bba18..69b223e 100644 --- a/sources/core/dom/node.js +++ b/sources/core/dom/node.js | |||
@@ -200,7 +200,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { | |||
200 | } | 200 | } |
201 | 201 | ||
202 | // IE8 rename HTML5 nodes by adding `:` at the begging of the tag name when the node is cloned, | 202 | // IE8 rename HTML5 nodes by adding `:` at the begging of the tag name when the node is cloned, |
203 | // so `<figure>` will be `<:figure>` after 'cloneNode'. We need to fix it (#13101). | 203 | // so `<figure>` will be `<:figure>` after 'cloneNode'. We need to fix it (http://dev.ckeditor.com/ticket/13101). |
204 | function renameNodes( node ) { | 204 | function renameNodes( node ) { |
205 | if ( node.type != CKEDITOR.NODE_ELEMENT && node.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT ) | 205 | if ( node.type != CKEDITOR.NODE_ELEMENT && node.type != CKEDITOR.NODE_DOCUMENT_FRAGMENT ) |
206 | return; | 206 | return; |
@@ -804,7 +804,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { | |||
804 | } else if ( trimmed.length < originalLength ) { | 804 | } else if ( trimmed.length < originalLength ) { |
805 | child.split( originalLength - trimmed.length ); | 805 | child.split( originalLength - trimmed.length ); |
806 | 806 | ||
807 | // IE BUG: child.remove() may raise JavaScript errors here. (#81) | 807 | // IE BUG: child.remove() may raise JavaScript errors here. (http://dev.ckeditor.com/ticket/81) |
808 | this.$.removeChild( this.$.firstChild ); | 808 | this.$.removeChild( this.$.firstChild ); |
809 | } | 809 | } |
810 | } | 810 | } |
@@ -829,7 +829,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { | |||
829 | child.split( trimmed.length ); | 829 | child.split( trimmed.length ); |
830 | 830 | ||
831 | // IE BUG: child.getNext().remove() may raise JavaScript errors here. | 831 | // IE BUG: child.getNext().remove() may raise JavaScript errors here. |
832 | // (#81) | 832 | // (http://dev.ckeditor.com/ticket/81) |
833 | this.$.lastChild.parentNode.removeChild( this.$.lastChild ); | 833 | this.$.lastChild.parentNode.removeChild( this.$.lastChild ); |
834 | } | 834 | } |
835 | } | 835 | } |
@@ -840,7 +840,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { | |||
840 | child = this.$.lastChild; | 840 | child = this.$.lastChild; |
841 | 841 | ||
842 | if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) { | 842 | if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) { |
843 | // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). | 843 | // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (http://dev.ckeditor.com/ticket/324). |
844 | child.parentNode.removeChild( child ); | 844 | child.parentNode.removeChild( child ); |
845 | } | 845 | } |
846 | } | 846 | } |
@@ -874,7 +874,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { | |||
874 | if ( this.type != CKEDITOR.NODE_ELEMENT ) | 874 | if ( this.type != CKEDITOR.NODE_ELEMENT ) |
875 | element = this.getParent(); | 875 | element = this.getParent(); |
876 | 876 | ||
877 | // Prevent Edge crash (#13609, #13919). | 877 | // Prevent Edge crash (http://dev.ckeditor.com/ticket/13609, http://dev.ckeditor.com/ticket/13919). |
878 | if ( CKEDITOR.env.edge && element && element.is( 'textarea', 'input' ) ) { | 878 | if ( CKEDITOR.env.edge && element && element.is( 'textarea', 'input' ) ) { |
879 | checkOnlyAttributes = true; | 879 | checkOnlyAttributes = true; |
880 | } | 880 | } |
diff --git a/sources/core/dom/nodelist.js b/sources/core/dom/nodelist.js index 165a415..07af314 100644 --- a/sources/core/dom/nodelist.js +++ b/sources/core/dom/nodelist.js | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | /** | 6 | /** |
7 | * Represents a list of {@link CKEDITOR.dom.node} objects. | 7 | * Represents a list of {@link CKEDITOR.dom.node} objects. |
8 | * It's a wrapper for native nodes list. | 8 | * It is a wrapper for a native nodes list. |
9 | * | 9 | * |
10 | * var nodeList = CKEDITOR.document.getBody().getChildren(); | 10 | * var nodeList = CKEDITOR.document.getBody().getChildren(); |
11 | * alert( nodeList.count() ); // number [0;N] | 11 | * alert( nodeList.count() ); // number [0;N] |
@@ -20,7 +20,7 @@ CKEDITOR.dom.nodeList = function( nativeList ) { | |||
20 | 20 | ||
21 | CKEDITOR.dom.nodeList.prototype = { | 21 | CKEDITOR.dom.nodeList.prototype = { |
22 | /** | 22 | /** |
23 | * Get count of nodes in this list. | 23 | * Gets the count of nodes in this list. |
24 | * | 24 | * |
25 | * @returns {Number} | 25 | * @returns {Number} |
26 | */ | 26 | */ |
@@ -29,7 +29,7 @@ CKEDITOR.dom.nodeList.prototype = { | |||
29 | }, | 29 | }, |
30 | 30 | ||
31 | /** | 31 | /** |
32 | * Get node from the list. | 32 | * Gets the node from the list. |
33 | * | 33 | * |
34 | * @returns {CKEDITOR.dom.node} | 34 | * @returns {CKEDITOR.dom.node} |
35 | */ | 35 | */ |
@@ -39,5 +39,16 @@ CKEDITOR.dom.nodeList.prototype = { | |||
39 | 39 | ||
40 | var $node = this.$[ index ]; | 40 | var $node = this.$[ index ]; |
41 | return $node ? new CKEDITOR.dom.node( $node ) : null; | 41 | return $node ? new CKEDITOR.dom.node( $node ) : null; |
42 | }, | ||
43 | |||
44 | /** | ||
45 | * Returns a node list as an array. | ||
46 | * | ||
47 | * @returns {CKEDITOR.dom.node[]} | ||
48 | */ | ||
49 | toArray: function() { | ||
50 | return CKEDITOR.tools.array.map( this.$, function( nativeEl ) { | ||
51 | return new CKEDITOR.dom.node( nativeEl ); | ||
52 | } ); | ||
42 | } | 53 | } |
43 | }; | 54 | }; |
diff --git a/sources/core/dom/range.js b/sources/core/dom/range.js index 6407074..742c24c 100644 --- a/sources/core/dom/range.js +++ b/sources/core/dom/range.js | |||
@@ -204,9 +204,14 @@ CKEDITOR.dom.range = function( root ) { | |||
204 | // This allows us to not think about startNode == endNode case later on. | 204 | // This allows us to not think about startNode == endNode case later on. |
205 | // We do that only when cloning, because in other cases we can safely split this text node | 205 | // We do that only when cloning, because in other cases we can safely split this text node |
206 | // and hence we can easily handle this case as many others. | 206 | // and hence we can easily handle this case as many others. |
207 | if ( isClone && endNode.type == CKEDITOR.NODE_TEXT && startNode.equals( endNode ) ) { | 207 | |
208 | startNode = range.document.createText( startNode.substring( startOffset, endOffset ) ); | 208 | // We need to handle situation when selection startNode is type of NODE_ELEMENT (#426). |
209 | docFrag.append( startNode ); | 209 | if ( isClone && |
210 | endNode.type == CKEDITOR.NODE_TEXT && | ||
211 | ( startNode.equals( endNode ) || ( startNode.type === CKEDITOR.NODE_ELEMENT && startNode.getFirst().equals( endNode ) ) ) ) { | ||
212 | |||
213 | // Here we should always be inside one text node. | ||
214 | docFrag.append( range.document.createText( endNode.substring( startOffset, endOffset ) ) ); | ||
210 | return; | 215 | return; |
211 | } | 216 | } |
212 | 217 | ||
@@ -214,7 +219,7 @@ CKEDITOR.dom.range = function( root ) { | |||
214 | // second part. The removal will be handled by the rest of the code. | 219 | // second part. The removal will be handled by the rest of the code. |
215 | if ( endNode.type == CKEDITOR.NODE_TEXT ) { | 220 | if ( endNode.type == CKEDITOR.NODE_TEXT ) { |
216 | // If Extract or Delete we can split the text node, | 221 | // If Extract or Delete we can split the text node, |
217 | // but if Clone (2), then we cannot modify the DOM (#11586) so we mark the text node for cloning. | 222 | // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark the text node for cloning. |
218 | if ( !isClone ) { | 223 | if ( !isClone ) { |
219 | endNode = endNode.split( endOffset ); | 224 | endNode = endNode.split( endOffset ); |
220 | } else { | 225 | } else { |
@@ -243,7 +248,7 @@ CKEDITOR.dom.range = function( root ) { | |||
243 | // be handled by the rest of the code . | 248 | // be handled by the rest of the code . |
244 | if ( startNode.type == CKEDITOR.NODE_TEXT ) { | 249 | if ( startNode.type == CKEDITOR.NODE_TEXT ) { |
245 | // If Extract or Delete we can split the text node, | 250 | // If Extract or Delete we can split the text node, |
246 | // but if Clone (2), then we cannot modify the DOM (#11586) so we mark | 251 | // but if Clone (2), then we cannot modify the DOM (http://dev.ckeditor.com/ticket/11586) so we mark |
247 | // the text node for cloning. | 252 | // the text node for cloning. |
248 | if ( !isClone ) { | 253 | if ( !isClone ) { |
249 | startNode.split( startOffset ); | 254 | startNode.split( startOffset ); |
@@ -373,7 +378,7 @@ CKEDITOR.dom.range = function( root ) { | |||
373 | // If we don't do that, in next iterations nodes will be appended to wrong parent. | 378 | // If we don't do that, in next iterations nodes will be appended to wrong parent. |
374 | // | 379 | // |
375 | // We can just take first child because the algorithm guarantees | 380 | // We can just take first child because the algorithm guarantees |
376 | // that this will be the only child on this level. (#13568) | 381 | // that this will be the only child on this level. (http://dev.ckeditor.com/ticket/13568) |
377 | levelParent = levelParent.getChild( 0 ); | 382 | levelParent = levelParent.getChild( 0 ); |
378 | } | 383 | } |
379 | } | 384 | } |
@@ -405,7 +410,7 @@ CKEDITOR.dom.range = function( root ) { | |||
405 | 410 | ||
406 | // When Extracting, move the removed node to the docFrag. | 411 | // When Extracting, move the removed node to the docFrag. |
407 | if ( isExtract ) { | 412 | if ( isExtract ) { |
408 | newParent.append( node ); | 413 | newParent.append( node, toStart ); |
409 | } | 414 | } |
410 | } | 415 | } |
411 | 416 | ||
@@ -574,7 +579,7 @@ CKEDITOR.dom.range = function( root ) { | |||
574 | // Tolerant bogus br when checking at the end of block. | 579 | // Tolerant bogus br when checking at the end of block. |
575 | // Reject any text node unless it's being bookmark | 580 | // Reject any text node unless it's being bookmark |
576 | // OR it's spaces. | 581 | // OR it's spaces. |
577 | // Reject any element unless it's being invisible empty. (#3883) | 582 | // Reject any element unless it's being invisible empty. (http://dev.ckeditor.com/ticket/3883) |
578 | return !checkStart && isBogus( node ) || | 583 | return !checkStart && isBogus( node ) || |
579 | node.type == CKEDITOR.NODE_ELEMENT && | 584 | node.type == CKEDITOR.NODE_ELEMENT && |
580 | node.is( CKEDITOR.dtd.$removeEmpty ); | 585 | node.is( CKEDITOR.dtd.$removeEmpty ); |
@@ -1052,7 +1057,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1052 | } | 1057 | } |
1053 | 1058 | ||
1054 | // Sometimes the endNode will come right before startNode for collapsed | 1059 | // Sometimes the endNode will come right before startNode for collapsed |
1055 | // ranges. Fix it. (#3780) | 1060 | // ranges. Fix it. (http://dev.ckeditor.com/ticket/3780) |
1056 | if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) | 1061 | if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) |
1057 | startNode = endNode; | 1062 | startNode = endNode; |
1058 | 1063 | ||
@@ -1324,13 +1329,13 @@ CKEDITOR.dom.range = function( root ) { | |||
1324 | 1329 | ||
1325 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); | 1330 | isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); |
1326 | } else { | 1331 | } else { |
1327 | // #12221 (Chrome) plus #11111 (Safari). | 1332 | // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari). |
1328 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; | 1333 | var offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; |
1329 | 1334 | ||
1330 | // If this is a visible element. | 1335 | // If this is a visible element. |
1331 | // We need to check for the bookmark attribute because IE insists on | 1336 | // We need to check for the bookmark attribute because IE insists on |
1332 | // rendering the display:none nodes we use for bookmarks. (#3363) | 1337 | // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363) |
1333 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) | 1338 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041) |
1334 | if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { | 1339 | if ( ( sibling.$.offsetWidth > offsetWidth0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { |
1335 | // We'll accept it only if we need | 1340 | // We'll accept it only if we need |
1336 | // whitespace, and this is an inline | 1341 | // whitespace, and this is an inline |
@@ -1399,7 +1404,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1399 | 1404 | ||
1400 | // Process the end boundary. This is basically the same | 1405 | // Process the end boundary. This is basically the same |
1401 | // code used for the start boundary, with small changes to | 1406 | // code used for the start boundary, with small changes to |
1402 | // make it work in the oposite side (to the right). This | 1407 | // make it work in the opposite side (to the right). This |
1403 | // makes it difficult to reuse the code here. So, fixes to | 1408 | // makes it difficult to reuse the code here. So, fixes to |
1404 | // the above code are likely to be replicated here. | 1409 | // the above code are likely to be replicated here. |
1405 | 1410 | ||
@@ -1482,7 +1487,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1482 | } | 1487 | } |
1483 | } | 1488 | } |
1484 | } else { | 1489 | } else { |
1485 | // Get the node right after the boudary to be checked | 1490 | // Get the node right after the boundary to be checked |
1486 | // first. | 1491 | // first. |
1487 | sibling = container.getChild( offset ); | 1492 | sibling = container.getChild( offset ); |
1488 | 1493 | ||
@@ -1525,8 +1530,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1525 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { | 1530 | } else if ( sibling.type == CKEDITOR.NODE_ELEMENT ) { |
1526 | // If this is a visible element. | 1531 | // If this is a visible element. |
1527 | // We need to check for the bookmark attribute because IE insists on | 1532 | // We need to check for the bookmark attribute because IE insists on |
1528 | // rendering the display:none nodes we use for bookmarks. (#3363) | 1533 | // rendering the display:none nodes we use for bookmarks. (http://dev.ckeditor.com/ticket/3363) |
1529 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041) | 1534 | // Line-breaks (br) are rendered with zero width, which we don't want to include. (http://dev.ckeditor.com/ticket/7041) |
1530 | if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { | 1535 | if ( ( sibling.$.offsetWidth > 0 || excludeBrs && sibling.is( 'br' ) ) && !sibling.data( 'cke-bookmark' ) ) { |
1531 | // We'll accept it only if we need | 1536 | // We'll accept it only if we need |
1532 | // whitespace, and this is an inline | 1537 | // whitespace, and this is an inline |
@@ -1617,7 +1622,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1617 | boundaryGuard = function( node ) { | 1622 | boundaryGuard = function( node ) { |
1618 | // We should not check contents of non-editable elements. It may happen | 1623 | // We should not check contents of non-editable elements. It may happen |
1619 | // that inline widget has display:table child which should not block range#enlarge. | 1624 | // that inline widget has display:table child which should not block range#enlarge. |
1620 | // When encoutered non-editable element... | 1625 | // When encountered non-editable element... |
1621 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { | 1626 | if ( node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'false' ) { |
1622 | if ( inNonEditable ) { | 1627 | if ( inNonEditable ) { |
1623 | // ... in which we already were, reset it (because we're leaving it) and return. | 1628 | // ... in which we already were, reset it (because we're leaving it) and return. |
@@ -1639,7 +1644,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1639 | blockBoundary = node; | 1644 | blockBoundary = node; |
1640 | return retval; | 1645 | return retval; |
1641 | }, | 1646 | }, |
1642 | // Record the encounted 'tailBr' for later use. | 1647 | // Record the encountered 'tailBr' for later use. |
1643 | tailBrGuard = function( node ) { | 1648 | tailBrGuard = function( node ) { |
1644 | var retval = boundaryGuard( node ); | 1649 | var retval = boundaryGuard( node ); |
1645 | if ( !retval && node.is && node.is( 'br' ) ) | 1650 | if ( !retval && node.is && node.is( 'br' ) ) |
@@ -1660,7 +1665,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1660 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || | 1665 | this.setStartAt( blockBoundary, !blockBoundary.is( 'br' ) && ( !enlargeable && this.checkStartOfBlock() || |
1661 | enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END ); | 1666 | enlargeable && blockBoundary.contains( enlargeable ) ) ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_AFTER_END ); |
1662 | 1667 | ||
1663 | // Avoid enlarging the range further when end boundary spans right after the BR. (#7490) | 1668 | // Avoid enlarging the range further when end boundary spans right after the BR. (http://dev.ckeditor.com/ticket/7490) |
1664 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { | 1669 | if ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) { |
1665 | var theRange = this.clone(); | 1670 | var theRange = this.clone(); |
1666 | walker = new CKEDITOR.dom.walker( theRange ); | 1671 | walker = new CKEDITOR.dom.walker( theRange ); |
@@ -1715,18 +1720,28 @@ CKEDITOR.dom.range = function( root ) { | |||
1715 | }, | 1720 | }, |
1716 | 1721 | ||
1717 | /** | 1722 | /** |
1718 | * Descrease the range to make sure that boundaries | 1723 | * Decreases the range to make sure that boundaries |
1719 | * always anchor beside text nodes or innermost element. | 1724 | * always anchor beside text nodes or the innermost element. |
1720 | * | 1725 | * |
1721 | * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}). | 1726 | * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}). |
1722 | * | 1727 | * |
1723 | * * {@link CKEDITOR#SHRINK_ELEMENT} - Shrink the range boundaries to the edge of the innermost element. | 1728 | * * {@link CKEDITOR#SHRINK_ELEMENT} – Shrinks the range boundaries to the edge of the innermost element. |
1724 | * * {@link CKEDITOR#SHRINK_TEXT} - Shrink the range boudaries to anchor by the side of enclosed text | 1729 | * * {@link CKEDITOR#SHRINK_TEXT} – Shrinks the range boundaries to anchor by the side of enclosed text |
1725 | * node, range remains if there's no text nodes on boundaries at all. | 1730 | * node. The range remains if there are no text nodes available on boundaries. |
1726 | * | 1731 | * |
1727 | * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node. | 1732 | * @param {Boolean} [selectContents=false] Whether the resulting range anchors at the inner OR outer boundary of the node. |
1733 | * @param {Boolean/Object} [options=true] If this parameter is of a Boolean type, it is treated as | ||
1734 | * `options.shrinkOnBlockBoundary`. This parameter was added in 4.7.0. | ||
1735 | * @param {Boolean} [options.shrinkOnBlockBoundary=true] Whether the block boundary should be included in | ||
1736 | * the shrunk range. | ||
1737 | * @param {Boolean} [options.skipBogus=false] Whether bogus `<br>` elements should be ignored while | ||
1738 | * `mode` is set to {@link CKEDITOR#SHRINK_TEXT}. This option was added in 4.7.0. | ||
1728 | */ | 1739 | */ |
1729 | shrink: function( mode, selectContents, shrinkOnBlockBoundary ) { | 1740 | shrink: function( mode, selectContents, options ) { |
1741 | var shrinkOnBlockBoundary = typeof options === 'boolean' ? options : | ||
1742 | ( options && typeof options.shrinkOnBlockBoundary === 'boolean' ? options.shrinkOnBlockBoundary : true ), | ||
1743 | skipBogus = options && options.skipBogus; | ||
1744 | |||
1730 | // Unable to shrink a collapsed range. | 1745 | // Unable to shrink a collapsed range. |
1731 | if ( !this.collapsed ) { | 1746 | if ( !this.collapsed ) { |
1732 | mode = mode || CKEDITOR.SHRINK_TEXT; | 1747 | mode = mode || CKEDITOR.SHRINK_TEXT; |
@@ -1749,7 +1764,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1749 | walkerRange.setStartAfter( startContainer ); | 1764 | walkerRange.setStartAfter( startContainer ); |
1750 | else { | 1765 | else { |
1751 | // Enlarge the range properly to avoid walker making | 1766 | // Enlarge the range properly to avoid walker making |
1752 | // DOM changes caused by triming the text nodes later. | 1767 | // DOM changes caused by trimming the text nodes later. |
1753 | walkerRange.setStartBefore( startContainer ); | 1768 | walkerRange.setStartBefore( startContainer ); |
1754 | moveStart = 0; | 1769 | moveStart = 0; |
1755 | } | 1770 | } |
@@ -1767,7 +1782,8 @@ CKEDITOR.dom.range = function( root ) { | |||
1767 | } | 1782 | } |
1768 | 1783 | ||
1769 | var walker = new CKEDITOR.dom.walker( walkerRange ), | 1784 | var walker = new CKEDITOR.dom.walker( walkerRange ), |
1770 | isBookmark = CKEDITOR.dom.walker.bookmark(); | 1785 | isBookmark = CKEDITOR.dom.walker.bookmark(), |
1786 | isBogus = CKEDITOR.dom.walker.bogus(); | ||
1771 | 1787 | ||
1772 | walker.evaluator = function( node ) { | 1788 | walker.evaluator = function( node ) { |
1773 | return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); | 1789 | return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); |
@@ -1775,6 +1791,11 @@ CKEDITOR.dom.range = function( root ) { | |||
1775 | 1791 | ||
1776 | var currentElement; | 1792 | var currentElement; |
1777 | walker.guard = function( node, movingOut ) { | 1793 | walker.guard = function( node, movingOut ) { |
1794 | // Skipping bogus before other cases (http://dev.ckeditor.com/ticket/17010). | ||
1795 | if ( skipBogus && isBogus( node ) ) { | ||
1796 | return true; | ||
1797 | } | ||
1798 | |||
1778 | if ( isBookmark( node ) ) | 1799 | if ( isBookmark( node ) ) |
1779 | return true; | 1800 | return true; |
1780 | 1801 | ||
@@ -1816,7 +1837,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1816 | 1837 | ||
1817 | /** | 1838 | /** |
1818 | * Inserts a node at the start of the range. The range will be expanded | 1839 | * Inserts a node at the start of the range. The range will be expanded |
1819 | * the contain the node. | 1840 | * to contain the node. |
1820 | * | 1841 | * |
1821 | * @param {CKEDITOR.dom.node} node | 1842 | * @param {CKEDITOR.dom.node} node |
1822 | */ | 1843 | */ |
@@ -1843,7 +1864,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1843 | }, | 1864 | }, |
1844 | 1865 | ||
1845 | /** | 1866 | /** |
1846 | * Moves the range to given position according to specified node. | 1867 | * Moves the range to a given position according to the specified node. |
1847 | * | 1868 | * |
1848 | * // HTML: <p>Foo <b>bar</b></p> | 1869 | * // HTML: <p>Foo <b>bar</b></p> |
1849 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); | 1870 | * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START ); |
@@ -1851,7 +1872,7 @@ CKEDITOR.dom.range = function( root ) { | |||
1851 | * | 1872 | * |
1852 | * See also {@link #setStartAt} and {@link #setEndAt}. | 1873 | * See also {@link #setStartAt} and {@link #setEndAt}. |
1853 | * | 1874 | * |
1854 | * @param {CKEDITOR.dom.node} node The node according to which position will be set. | 1875 | * @param {CKEDITOR.dom.node} node The node according to which the position will be set. |
1855 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, | 1876 | * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START}, |
1856 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, | 1877 | * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END}, |
1857 | * {@link CKEDITOR#POSITION_AFTER_END}. | 1878 | * {@link CKEDITOR#POSITION_AFTER_END}. |
@@ -2118,7 +2139,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2118 | // So even if the initial range was placed before the bogus <br>, after creating the bookmark it | 2139 | // So even if the initial range was placed before the bogus <br>, after creating the bookmark it |
2119 | // is placed before the bookmark. | 2140 | // is placed before the bookmark. |
2120 | // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case. | 2141 | // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case. |
2121 | // We remove incorrectly placed one and add a brand new one. (#13001) | 2142 | // We remove incorrectly placed one and add a brand new one. (http://dev.ckeditor.com/ticket/13001) |
2122 | var bogus = fixedBlock.getBogus(); | 2143 | var bogus = fixedBlock.getBogus(); |
2123 | if ( bogus ) { | 2144 | if ( bogus ) { |
2124 | bogus.remove(); | 2145 | bogus.remove(); |
@@ -2340,7 +2361,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2340 | this.trim( 0, 1 ); | 2361 | this.trim( 0, 1 ); |
2341 | } | 2362 | } |
2342 | 2363 | ||
2343 | // Antecipate the trim() call here, so the walker will not make | 2364 | // Anticipate the trim() call here, so the walker will not make |
2344 | // changes to the DOM, which would not get reflected into this | 2365 | // changes to the DOM, which would not get reflected into this |
2345 | // range otherwise. | 2366 | // range otherwise. |
2346 | this.trim(); | 2367 | this.trim(); |
@@ -2379,7 +2400,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2379 | this.trim( 1, 0 ); | 2400 | this.trim( 1, 0 ); |
2380 | } | 2401 | } |
2381 | 2402 | ||
2382 | // Antecipate the trim() call here, so the walker will not make | 2403 | // Anticipate the trim() call here, so the walker will not make |
2383 | // changes to the DOM, which would not get reflected into this | 2404 | // changes to the DOM, which would not get reflected into this |
2384 | // range otherwise. | 2405 | // range otherwise. |
2385 | this.trim(); | 2406 | this.trim(); |
@@ -2636,7 +2657,7 @@ CKEDITOR.dom.range = function( root ) { | |||
2636 | getEnclosedNode: function() { | 2657 | getEnclosedNode: function() { |
2637 | var walkerRange = this.clone(); | 2658 | var walkerRange = this.clone(); |
2638 | 2659 | ||
2639 | // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780) | 2660 | // Optimize and analyze the range to avoid DOM destructive nature of walker. (http://dev.ckeditor.com/ticket/5780) |
2640 | walkerRange.optimize(); | 2661 | walkerRange.optimize(); |
2641 | if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) | 2662 | if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) |
2642 | return null; | 2663 | return null; |
@@ -2703,6 +2724,53 @@ CKEDITOR.dom.range = function( root ) { | |||
2703 | getPreviousEditableNode: getNextEditableNode( 1 ), | 2724 | getPreviousEditableNode: getNextEditableNode( 1 ), |
2704 | 2725 | ||
2705 | /** | 2726 | /** |
2727 | * Returns any table element, like `td`, `tbody`, `table` etc. from a given range. The element | ||
2728 | * is returned only if the range is contained within one table (might be a nested | ||
2729 | * table, but it cannot be two different tables on the same DOM level). | ||
2730 | * | ||
2731 | * @private | ||
2732 | * @since 4.7 | ||
2733 | * @param {Object} [tableElements] Mapping of element names that should be considered. | ||
2734 | * @returns {CKEDITOR.dom.element/null} | ||
2735 | */ | ||
2736 | _getTableElement: function( tableElements ) { | ||
2737 | tableElements = tableElements || { | ||
2738 | td: 1, | ||
2739 | th: 1, | ||
2740 | tr: 1, | ||
2741 | tbody: 1, | ||
2742 | thead: 1, | ||
2743 | tfoot: 1, | ||
2744 | table: 1 | ||
2745 | }; | ||
2746 | |||
2747 | var start = this.startContainer, | ||
2748 | end = this.endContainer, | ||
2749 | startTable = start.getAscendant( 'table', true ), | ||
2750 | endTable = end.getAscendant( 'table', true ); | ||
2751 | |||
2752 | // Super weird edge case in Safari: if there is a table with only one cell inside and that cell | ||
2753 | // is selected, then the end boundary of the table is moved into editor's editable. | ||
2754 | // That case is also present when selecting the last cell inside nested table. | ||
2755 | if ( CKEDITOR.env.safari && startTable && end.equals( this.root ) ) { | ||
2756 | return start.getAscendant( tableElements, true ); | ||
2757 | } | ||
2758 | |||
2759 | if ( this.getEnclosedNode() ) { | ||
2760 | return this.getEnclosedNode().getAscendant( tableElements, true ); | ||
2761 | } | ||
2762 | |||
2763 | // Ensure that selection starts and ends in the same table or one of the table is inside the other. | ||
2764 | if ( startTable && endTable && ( startTable.equals( endTable ) || startTable.contains( endTable ) || | ||
2765 | endTable.contains( startTable ) ) ) { | ||
2766 | |||
2767 | return start.getAscendant( tableElements, true ); | ||
2768 | } | ||
2769 | |||
2770 | return null; | ||
2771 | }, | ||
2772 | |||
2773 | /** | ||
2706 | * Scrolls the start of current range into view. | 2774 | * Scrolls the start of current range into view. |
2707 | */ | 2775 | */ |
2708 | scrollIntoView: function() { | 2776 | scrollIntoView: function() { |
@@ -2819,10 +2887,10 @@ CKEDITOR.dom.range = function( root ) { | |||
2819 | continue; | 2887 | continue; |
2820 | } | 2888 | } |
2821 | 2889 | ||
2822 | // It's not enough to get elements from common ancestor, because it migth contain too many matches. | 2890 | // It's not enough to get elements from common ancestor, because it might contain too many matches. |
2823 | // We need to ensure that returned items are between boundary points. | 2891 | // We need to ensure that returned items are between boundary points. |
2824 | isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem ); | 2892 | isStartGood = ( curItem.getPosition( boundaries.startNode ) & CKEDITOR.POSITION_FOLLOWING ) || boundaries.startNode.equals( curItem ); |
2825 | isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ); | 2893 | isEndGood = ( curItem.getPosition( boundaries.endNode ) & ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IS_CONTAINED ) ) || boundaries.endNode.equals( curItem ); |
2826 | 2894 | ||
2827 | if ( isStartGood && isEndGood ) { | 2895 | if ( isStartGood && isEndGood ) { |
2828 | ret.push( curItem ); | 2896 | ret.push( curItem ); |
@@ -2834,6 +2902,62 @@ CKEDITOR.dom.range = function( root ) { | |||
2834 | } | 2902 | } |
2835 | }; | 2903 | }; |
2836 | 2904 | ||
2905 | /** | ||
2906 | * Merges every subsequent range in given set, returning a smaller array of ranges. | ||
2907 | * | ||
2908 | * Note that each range in the returned value will be enlarged with `CKEDITOR.ENLARGE_ELEMENT` value. | ||
2909 | * | ||
2910 | * @since 4.7.0 | ||
2911 | * @static | ||
2912 | * @param {CKEDITOR.dom.range[]} ranges | ||
2913 | * @returns {CKEDITOR.dom.range[]} Set of merged ranges. | ||
2914 | * @member CKEDITOR.dom.range | ||
2915 | */ | ||
2916 | CKEDITOR.dom.range.mergeRanges = function( ranges ) { | ||
2917 | return CKEDITOR.tools.array.reduce( ranges, function( ret, rng ) { | ||
2918 | // Last range ATM. | ||
2919 | var lastRange = ret[ ret.length - 1 ], | ||
2920 | isContinuation = false; | ||
2921 | |||
2922 | // Make a clone, we don't want to modify input. | ||
2923 | rng = rng.clone(); | ||
2924 | rng.enlarge( CKEDITOR.ENLARGE_ELEMENT ); | ||
2925 | |||
2926 | if ( lastRange ) { | ||
2927 | // The trick is to create a range spanning the gap between the two ranges. Then iterate over | ||
2928 | // each node found in this gap. If it contains anything other than whitespace, then it means it | ||
2929 | // is not a continuation. | ||
2930 | var gapRange = new CKEDITOR.dom.range( rng.root ), | ||
2931 | walker = new CKEDITOR.dom.walker( gapRange ), | ||
2932 | isWhitespace = CKEDITOR.dom.walker.whitespaces(), | ||
2933 | nodeInBetween; | ||
2934 | |||
2935 | gapRange.setStart( lastRange.endContainer, lastRange.endOffset ); | ||
2936 | gapRange.setEnd( rng.startContainer, rng.startOffset ); | ||
2937 | |||
2938 | nodeInBetween = walker.next(); | ||
2939 | |||
2940 | while ( isWhitespace( nodeInBetween ) || rng.endContainer.equals( nodeInBetween ) ) { | ||
2941 | // We don't care about whitespaces, and range container. Also we skip the endContainer, | ||
2942 | // as it will also be provided by the iterator (as it visits it's opening tag). | ||
2943 | nodeInBetween = walker.next(); | ||
2944 | } | ||
2945 | |||
2946 | // Simply, if anything has been found there's a content in between the two. | ||
2947 | isContinuation = !nodeInBetween; | ||
2948 | } | ||
2949 | |||
2950 | if ( isContinuation ) { | ||
2951 | // If last range ends, where the current range starts, then let's merge it. | ||
2952 | lastRange.setEnd( rng.endContainer, rng.endOffset ); | ||
2953 | } else { | ||
2954 | // In other case just push cur range into the stack. | ||
2955 | ret.push( rng ); | ||
2956 | } | ||
2957 | |||
2958 | return ret; | ||
2959 | }, [] ); | ||
2960 | }; | ||
2837 | 2961 | ||
2838 | } )(); | 2962 | } )(); |
2839 | 2963 | ||
diff --git a/sources/core/dom/rangelist.js b/sources/core/dom/rangelist.js index 250dfd9..15ccb88 100644 --- a/sources/core/dom/rangelist.js +++ b/sources/core/dom/rangelist.js | |||
@@ -164,7 +164,7 @@ | |||
164 | }; | 164 | }; |
165 | 165 | ||
166 | // Update the specified range which has been mangled by previous insertion of | 166 | // Update the specified range which has been mangled by previous insertion of |
167 | // range bookmark nodes.(#3256) | 167 | // range bookmark nodes.(http://dev.ckeditor.com/ticket/3256) |
168 | function updateDirtyRange( bookmark, dirtyRange, checkEnd ) { | 168 | function updateDirtyRange( bookmark, dirtyRange, checkEnd ) { |
169 | var serializable = bookmark.serializable, | 169 | var serializable = bookmark.serializable, |
170 | container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ], | 170 | container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ], |
diff --git a/sources/core/dom/text.js b/sources/core/dom/text.js index ce20ffe..e77a3d9 100644 --- a/sources/core/dom/text.js +++ b/sources/core/dom/text.js | |||
@@ -106,7 +106,7 @@ CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, { | |||
106 | retval.insertAfter( this ); | 106 | retval.insertAfter( this ); |
107 | } else { | 107 | } else { |
108 | // IE BUG: IE8+ does not update the childNodes array in DOM after splitText(), | 108 | // IE BUG: IE8+ does not update the childNodes array in DOM after splitText(), |
109 | // we need to make some DOM changes to make it update. (#3436) | 109 | // we need to make some DOM changes to make it update. (http://dev.ckeditor.com/ticket/3436) |
110 | var workaround = doc.createText( '' ); | 110 | var workaround = doc.createText( '' ); |
111 | workaround.insertAfter( retval ); | 111 | workaround.insertAfter( retval ); |
112 | workaround.remove(); | 112 | workaround.remove(); |
diff --git a/sources/core/dom/walker.js b/sources/core/dom/walker.js index cec4574..8665909 100644 --- a/sources/core/dom/walker.js +++ b/sources/core/dom/walker.js | |||
@@ -319,7 +319,7 @@ | |||
319 | */ | 319 | */ |
320 | CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) { | 320 | CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) { |
321 | // Whether element is in normal page flow. Floated or positioned elements are out of page flow. | 321 | // Whether element is in normal page flow. Floated or positioned elements are out of page flow. |
322 | // Don't consider floated or positioned formatting as block boundary, fall back to dtd check in that case. (#6297) | 322 | // Don't consider floated or positioned formatting as block boundary, fall back to dtd check in that case. (http://dev.ckeditor.com/ticket/6297) |
323 | var inPageFlow = this.getComputedStyle( 'float' ) == 'none' && !( this.getComputedStyle( 'position' ) in outOfFlowPositions ); | 323 | var inPageFlow = this.getComputedStyle( 'float' ) == 'none' && !( this.getComputedStyle( 'position' ) in outOfFlowPositions ); |
324 | 324 | ||
325 | if ( inPageFlow && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ) | 325 | if ( inPageFlow && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ) |
@@ -388,7 +388,7 @@ | |||
388 | return function( node ) { | 388 | return function( node ) { |
389 | var isWhitespace; | 389 | var isWhitespace; |
390 | if ( node && node.type == CKEDITOR.NODE_TEXT ) { | 390 | if ( node && node.type == CKEDITOR.NODE_TEXT ) { |
391 | // Whitespace, as well as the Filling Char Sequence text node used in Webkit. (#9384, #13816) | 391 | // Whitespace, as well as the Filling Char Sequence text node used in Webkit. (http://dev.ckeditor.com/ticket/9384, http://dev.ckeditor.com/ticket/13816) |
392 | isWhitespace = !CKEDITOR.tools.trim( node.getText() ) || | 392 | isWhitespace = !CKEDITOR.tools.trim( node.getText() ) || |
393 | CKEDITOR.env.webkit && node.getText() == CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE; | 393 | CKEDITOR.env.webkit && node.getText() == CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE; |
394 | } | 394 | } |
@@ -406,7 +406,7 @@ | |||
406 | */ | 406 | */ |
407 | CKEDITOR.dom.walker.invisible = function( isReject ) { | 407 | CKEDITOR.dom.walker.invisible = function( isReject ) { |
408 | var whitespace = CKEDITOR.dom.walker.whitespaces(), | 408 | var whitespace = CKEDITOR.dom.walker.whitespaces(), |
409 | // #12221 (Chrome) plus #11111 (Safari). | 409 | // http://dev.ckeditor.com/ticket/12221 (Chrome) plus http://dev.ckeditor.com/ticket/11111 (Safari). |
410 | offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; | 410 | offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; |
411 | 411 | ||
412 | return function( node ) { | 412 | return function( node ) { |
@@ -422,7 +422,7 @@ | |||
422 | // Nodes that take no spaces in wysiwyg: | 422 | // Nodes that take no spaces in wysiwyg: |
423 | // 1. White-spaces but not including NBSP. | 423 | // 1. White-spaces but not including NBSP. |
424 | // 2. Empty inline elements, e.g. <b></b>. | 424 | // 2. Empty inline elements, e.g. <b></b>. |
425 | // 3. <br> elements (bogus, surrounded by text) (#12423). | 425 | // 3. <br> elements (bogus, surrounded by text) (http://dev.ckeditor.com/ticket/12423). |
426 | invisible = node.$.offsetWidth <= offsetWidth0; | 426 | invisible = node.$.offsetWidth <= offsetWidth0; |
427 | } | 427 | } |
428 | 428 | ||
@@ -636,7 +636,7 @@ | |||
636 | * @returns {CKEDITOR.dom.node/Boolean} Bogus node or `false`. | 636 | * @returns {CKEDITOR.dom.node/Boolean} Bogus node or `false`. |
637 | */ | 637 | */ |
638 | CKEDITOR.dom.element.prototype.getBogus = function() { | 638 | CKEDITOR.dom.element.prototype.getBogus = function() { |
639 | // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070). | 639 | // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (http://dev.ckeditor.com/ticket/7070). |
640 | var tail = this; | 640 | var tail = this; |
641 | do { | 641 | do { |
642 | tail = tail.getPreviousSourceNode(); | 642 | tail = tail.getPreviousSourceNode(); |
diff --git a/sources/core/editable.js b/sources/core/editable.js index c50ec7f..6b3fa9f 100644 --- a/sources/core/editable.js +++ b/sources/core/editable.js | |||
@@ -76,7 +76,7 @@ | |||
76 | 76 | ||
77 | // [Edge] Starting from EdgeHTML 14.14393, it does not support `setActive`. We need to use focus which | 77 | // [Edge] Starting from EdgeHTML 14.14393, it does not support `setActive`. We need to use focus which |
78 | // causes unexpected scroll. Store scrollTop value so it can be restored after focusing editor. | 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. (#14825) | 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 ) ) { | 80 | if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 && !this.hasFocus && this.getDocument().equals( CKEDITOR.document ) ) { |
81 | this.editor._.previousScrollTop = this.$.scrollTop; | 81 | this.editor._.previousScrollTop = this.$.scrollTop; |
82 | } | 82 | } |
@@ -87,7 +87,15 @@ | |||
87 | if ( CKEDITOR.env.ie && !( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) && this.getDocument().equals( CKEDITOR.document ) ) { | 87 | if ( CKEDITOR.env.ie && !( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) && this.getDocument().equals( CKEDITOR.document ) ) { |
88 | this.$.setActive(); | 88 | this.$.setActive(); |
89 | } else { | 89 | } else { |
90 | this.$.focus(); | 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 | } | ||
91 | } | 99 | } |
92 | } catch ( e ) { | 100 | } catch ( e ) { |
93 | // 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. |
@@ -95,7 +103,7 @@ | |||
95 | throw e; | 103 | throw e; |
96 | } | 104 | } |
97 | 105 | ||
98 | // Remedy if Safari doens't applies focus properly. (#279) | 106 | // Remedy if Safari doens't applies focus properly. (http://dev.ckeditor.com/ticket/279) |
99 | if ( CKEDITOR.env.safari && !this.isInline() ) { | 107 | if ( CKEDITOR.env.safari && !this.isInline() ) { |
100 | active = CKEDITOR.document.getActive(); | 108 | active = CKEDITOR.document.getActive(); |
101 | if ( !active.equals( this.getWindow().getFrame() ) ) | 109 | if ( !active.equals( this.getWindow().getFrame() ) ) |
@@ -117,7 +125,7 @@ | |||
117 | 125 | ||
118 | // 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 |
119 | // 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 |
120 | // to allow edit their contents. (#4682) | 128 | // to allow edit their contents. (http://dev.ckeditor.com/ticket/4682) |
121 | fn = isNotBubbling( fn, this ); | 129 | fn = isNotBubbling( fn, this ); |
122 | args[ 0 ] = name; | 130 | args[ 0 ] = name; |
123 | args[ 1 ] = fn; | 131 | args[ 1 ] = fn; |
@@ -252,7 +260,7 @@ | |||
252 | * @param {String} text | 260 | * @param {String} text |
253 | */ | 261 | */ |
254 | insertText: function( text ) { | 262 | insertText: function( text ) { |
255 | // Focus the editor before calling transformPlainTextToHtml. (#12726) | 263 | // Focus the editor before calling transformPlainTextToHtml. (http://dev.ckeditor.com/ticket/12726) |
256 | this.editor.focus(); | 264 | this.editor.focus(); |
257 | this.insertHtml( this.transformPlainTextToHtml( text ), 'text' ); | 265 | this.insertHtml( this.transformPlainTextToHtml( text ), 'text' ); |
258 | }, | 266 | }, |
@@ -350,7 +358,7 @@ | |||
350 | insertElement: function( element, range ) { | 358 | insertElement: function( element, range ) { |
351 | var editor = this.editor; | 359 | var editor = this.editor; |
352 | 360 | ||
353 | // Prepare for the insertion. For example - focus editor (#11848). | 361 | // Prepare for the insertion. For example - focus editor (http://dev.ckeditor.com/ticket/11848). |
354 | editor.focus(); | 362 | editor.focus(); |
355 | editor.fire( 'saveSnapshot' ); | 363 | editor.fire( 'saveSnapshot' ); |
356 | 364 | ||
@@ -363,12 +371,12 @@ | |||
363 | range = selection.getRanges()[ 0 ]; | 371 | range = selection.getRanges()[ 0 ]; |
364 | } | 372 | } |
365 | 373 | ||
366 | // 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). |
367 | if ( this.insertElementIntoRange( element, range ) ) { | 375 | if ( this.insertElementIntoRange( element, range ) ) { |
368 | range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END ); | 376 | range.moveToPosition( element, CKEDITOR.POSITION_AFTER_END ); |
369 | 377 | ||
370 | // 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 |
371 | // optimized. (#3100,#5436,#8950) | 379 | // optimized. (http://dev.ckeditor.com/ticket/3100,http://dev.ckeditor.com/ticket/5436,http://dev.ckeditor.com/ticket/8950) |
372 | if ( isBlock ) { | 380 | if ( isBlock ) { |
373 | // Find next, meaningful element. | 381 | // Find next, meaningful element. |
374 | var next = element.getNext( function( node ) { | 382 | var next = element.getNext( function( node ) { |
@@ -456,7 +464,7 @@ | |||
456 | range.splitElement( current ); | 464 | range.splitElement( current ); |
457 | 465 | ||
458 | // 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, |
459 | // simply replace it with the inserting block.(#3664) | 467 | // simply replace it with the inserting block.(http://dev.ckeditor.com/ticket/3664) |
460 | else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { | 468 | else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { |
461 | range.setStartBefore( current ); | 469 | range.setStartBefore( current ); |
462 | range.collapse( true ); | 470 | range.collapse( true ); |
@@ -770,7 +778,7 @@ | |||
770 | range.checkEndOfBlock() && | 778 | range.checkEndOfBlock() && |
771 | path.block && | 779 | path.block && |
772 | !range.root.equals( path.block ) && | 780 | !range.root.equals( path.block ) && |
773 | // Do not remove a block with bookmarks. (#13465) | 781 | // Do not remove a block with bookmarks. (http://dev.ckeditor.com/ticket/13465) |
774 | !hasBookmarks( path.block ) ) { | 782 | !hasBookmarks( path.block ) ) { |
775 | range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START ); | 783 | range.moveToPosition( path.block, CKEDITOR.POSITION_BEFORE_START ); |
776 | path.block.remove(); | 784 | path.block.remove(); |
@@ -832,7 +840,7 @@ | |||
832 | 840 | ||
833 | // IE considers control-type element as separate | 841 | // IE considers control-type element as separate |
834 | // focus host when selected, avoid destroying the | 842 | // focus host when selected, avoid destroying the |
835 | // selection in such case. (#5812) (#8949) | 843 | // selection in such case. (http://dev.ckeditor.com/ticket/5812) (http://dev.ckeditor.com/ticket/8949) |
836 | if ( ieSel && ieSel.type == 'Control' ) | 844 | if ( ieSel && ieSel.type == 'Control' ) |
837 | return; | 845 | return; |
838 | 846 | ||
@@ -884,14 +892,14 @@ | |||
884 | }, null, null, -1 ); | 892 | }, null, null, -1 ); |
885 | 893 | ||
886 | if ( CKEDITOR.env.webkit ) { | 894 | if ( CKEDITOR.env.webkit ) { |
887 | // [WebKit] Save scrollTop value so it can be used when restoring locked selection. (#14659) | 895 | // [WebKit] Save scrollTop value so it can be used when restoring locked selection. (http://dev.ckeditor.com/ticket/14659) |
888 | this.on( 'scroll', function() { | 896 | this.on( 'scroll', function() { |
889 | editor._.previousScrollTop = editor.editable().$.scrollTop; | 897 | editor._.previousScrollTop = editor.editable().$.scrollTop; |
890 | }, null, null, -1 ); | 898 | }, null, null, -1 ); |
891 | } | 899 | } |
892 | 900 | ||
893 | // [Edge] This is the other part of the workaround for Edge which restores saved | 901 | // [Edge] This is the other part of the workaround for Edge which restores saved |
894 | // scrollTop value and removes listener which is not needed anymore. (#14825) | 902 | // scrollTop value and removes listener which is not needed anymore. (http://dev.ckeditor.com/ticket/14825) |
895 | if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) { | 903 | if ( CKEDITOR.env.edge && CKEDITOR.env.version > 14 ) { |
896 | 904 | ||
897 | var fixScrollOnFocus = function() { | 905 | var fixScrollOnFocus = function() { |
@@ -967,7 +975,7 @@ | |||
967 | // Pass this configuration to styles system. | 975 | // Pass this configuration to styles system. |
968 | this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); | 976 | this.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling ); |
969 | 977 | ||
970 | // 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) |
971 | this.attachListener( this, 'click', function( evt ) { | 979 | this.attachListener( this, 'click', function( evt ) { |
972 | evt = evt.data; | 980 | evt = evt.data; |
973 | 981 | ||
@@ -980,7 +988,7 @@ | |||
980 | var backspaceOrDelete = { 8: 1, 46: 1 }; | 988 | var backspaceOrDelete = { 8: 1, 46: 1 }; |
981 | 989 | ||
982 | // Override keystrokes which should have deletion behavior | 990 | // Override keystrokes which should have deletion behavior |
983 | // on fully selected element . (#4047) (#7645) | 991 | // on fully selected element . (http://dev.ckeditor.com/ticket/4047) (http://dev.ckeditor.com/ticket/7645) |
984 | this.attachListener( editor, 'key', function( evt ) { | 992 | this.attachListener( editor, 'key', function( evt ) { |
985 | if ( editor.readOnly ) | 993 | if ( editor.readOnly ) |
986 | return true; | 994 | return true; |
@@ -990,10 +998,15 @@ | |||
990 | var keyCode = evt.data.domEvent.getKey(), | 998 | var keyCode = evt.data.domEvent.getKey(), |
991 | isHandled; | 999 | isHandled; |
992 | 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 | |||
993 | // Backspace OR Delete. | 1007 | // Backspace OR Delete. |
994 | if ( keyCode in backspaceOrDelete ) { | 1008 | if ( keyCode in backspaceOrDelete ) { |
995 | var sel = editor.getSelection(), | 1009 | var selected, |
996 | selected, | ||
997 | range = sel.getRanges()[ 0 ], | 1010 | range = sel.getRanges()[ 0 ], |
998 | path = range.startPath(), | 1011 | path = range.startPath(), |
999 | block, | 1012 | block, |
@@ -1001,16 +1014,17 @@ | |||
1001 | next, | 1014 | next, |
1002 | rtl = keyCode == 8; | 1015 | rtl = keyCode == 8; |
1003 | 1016 | ||
1017 | |||
1004 | if ( | 1018 | if ( |
1005 | // [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) |
1006 | ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) || | 1020 | ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 && ( selected = sel.getSelectedElement() ) ) || |
1007 | // 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) |
1008 | ( selected = getSelectedTableList( sel ) ) ) { | 1022 | ( selected = getSelectedTableList( sel ) ) ) { |
1009 | // Make undo snapshot. | 1023 | // Make undo snapshot. |
1010 | editor.fire( 'saveSnapshot' ); | 1024 | editor.fire( 'saveSnapshot' ); |
1011 | 1025 | ||
1012 | // 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 |
1013 | // break up the selection, safely manage it here. (#4795) | 1027 | // break up the selection, safely manage it here. (http://dev.ckeditor.com/ticket/4795) |
1014 | range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); | 1028 | range.moveToPosition( selected, CKEDITOR.POSITION_BEFORE_START ); |
1015 | // Remove the control manually. | 1029 | // Remove the control manually. |
1016 | selected.remove(); | 1030 | selected.remove(); |
@@ -1020,7 +1034,7 @@ | |||
1020 | 1034 | ||
1021 | isHandled = 1; | 1035 | isHandled = 1; |
1022 | } else if ( range.collapsed ) { | 1036 | } else if ( range.collapsed ) { |
1023 | // Handle the following special cases: (#6217) | 1037 | // Handle the following special cases: (http://dev.ckeditor.com/ticket/6217) |
1024 | // 1. Del/Backspace key before/after table; | 1038 | // 1. Del/Backspace key before/after table; |
1025 | // 2. Backspace Key after start of table. | 1039 | // 2. Backspace Key after start of table. |
1026 | if ( ( block = path.block ) && | 1040 | if ( ( block = path.block ) && |
@@ -1095,28 +1109,28 @@ | |||
1095 | editor.fire( 'doubleclick', data ); | 1109 | editor.fire( 'doubleclick', data ); |
1096 | } ); | 1110 | } ); |
1097 | 1111 | ||
1098 | // Prevent automatic submission in IE #6336 | 1112 | // Prevent automatic submission in IE http://dev.ckeditor.com/ticket/6336 |
1099 | CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick ); | 1113 | CKEDITOR.env.ie && this.attachListener( this, 'click', blockInputClick ); |
1100 | 1114 | ||
1101 | // 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) |
1102 | // We apply same behavior for IE Edge. (#13386) | 1116 | // We apply same behavior for IE Edge. (http://dev.ckeditor.com/ticket/13386) |
1103 | if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) { | 1117 | if ( !CKEDITOR.env.ie || CKEDITOR.env.edge ) { |
1104 | this.attachListener( this, 'mousedown', function( ev ) { | 1118 | this.attachListener( this, 'mousedown', function( ev ) { |
1105 | var control = ev.data.getTarget(); | 1119 | var control = ev.data.getTarget(); |
1106 | // #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 |
1107 | // 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, |
1108 | // and therefore those elements are correctly selected by this code. | 1122 | // and therefore those elements are correctly selected by this code. |
1109 | if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) { | 1123 | if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) && !control.isReadOnly() ) { |
1110 | editor.getSelection().selectElement( control ); | 1124 | editor.getSelection().selectElement( control ); |
1111 | 1125 | ||
1112 | // Prevent focus from stealing from the editable. (#9515) | 1126 | // Prevent focus from stealing from the editable. (http://dev.ckeditor.com/ticket/9515) |
1113 | if ( control.is( 'input', 'textarea', 'select' ) ) | 1127 | if ( control.is( 'input', 'textarea', 'select' ) ) |
1114 | ev.data.preventDefault(); | 1128 | ev.data.preventDefault(); |
1115 | } | 1129 | } |
1116 | } ); | 1130 | } ); |
1117 | } | 1131 | } |
1118 | 1132 | ||
1119 | // 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) |
1120 | if ( CKEDITOR.env.edge ) { | 1134 | if ( CKEDITOR.env.edge ) { |
1121 | this.attachListener( this, 'mouseup', function( ev ) { | 1135 | this.attachListener( this, 'mouseup', function( ev ) { |
1122 | var selectedElement = ev.data.getTarget(); | 1136 | var selectedElement = ev.data.getTarget(); |
@@ -1127,7 +1141,7 @@ | |||
1127 | } | 1141 | } |
1128 | 1142 | ||
1129 | // Prevent right click from selecting an empty block even | 1143 | // Prevent right click from selecting an empty block even |
1130 | // when selection is anchored inside it. (#5845) | 1144 | // when selection is anchored inside it. (http://dev.ckeditor.com/ticket/5845) |
1131 | if ( CKEDITOR.env.gecko ) { | 1145 | if ( CKEDITOR.env.gecko ) { |
1132 | this.attachListener( this, 'mouseup', function( ev ) { | 1146 | this.attachListener( this, 'mouseup', function( ev ) { |
1133 | if ( ev.data.$.button == 2 ) { | 1147 | if ( ev.data.$.button == 2 ) { |
@@ -1158,7 +1172,7 @@ | |||
1158 | } | 1172 | } |
1159 | 1173 | ||
1160 | // Prevent Webkit/Blink from going rogue when joining | 1174 | // Prevent Webkit/Blink from going rogue when joining |
1161 | // blocks on BACKSPACE/DEL (#11861,#9998). | 1175 | // blocks on BACKSPACE/DEL (http://dev.ckeditor.com/ticket/11861,http://dev.ckeditor.com/ticket/9998). |
1162 | if ( CKEDITOR.env.webkit ) { | 1176 | if ( CKEDITOR.env.webkit ) { |
1163 | this.attachListener( editor, 'key', function( evt ) { | 1177 | this.attachListener( editor, 'key', function( evt ) { |
1164 | if ( editor.readOnly ) { | 1178 | if ( editor.readOnly ) { |
@@ -1172,8 +1186,14 @@ | |||
1172 | if ( !( key in backspaceOrDelete ) ) | 1186 | if ( !( key in backspaceOrDelete ) ) |
1173 | return; | 1187 | return; |
1174 | 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 | |||
1175 | var backspace = key == 8, | 1195 | var backspace = key == 8, |
1176 | range = editor.getSelection().getRanges()[ 0 ], | 1196 | range = sel.getRanges()[ 0 ], |
1177 | startPath = range.startPath(); | 1197 | startPath = range.startPath(); |
1178 | 1198 | ||
1179 | if ( range.collapsed ) { | 1199 | if ( range.collapsed ) { |
@@ -1184,7 +1204,7 @@ | |||
1184 | return; | 1204 | return; |
1185 | } | 1205 | } |
1186 | 1206 | ||
1187 | // Scroll to the new position of the caret (#11960). | 1207 | // Scroll to the new position of the caret (http://dev.ckeditor.com/ticket/11960). |
1188 | editor.getSelection().scrollIntoView(); | 1208 | editor.getSelection().scrollIntoView(); |
1189 | editor.fire( 'saveSnapshot' ); | 1209 | editor.fire( 'saveSnapshot' ); |
1190 | 1210 | ||
@@ -1241,6 +1261,7 @@ | |||
1241 | * @member CKEDITOR.editor | 1261 | * @member CKEDITOR.editor |
1242 | * @param {CKEDITOR.dom.element/CKEDITOR.editable} [elementOrEditable] The | 1262 | * @param {CKEDITOR.dom.element/CKEDITOR.editable} [elementOrEditable] The |
1243 | * 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. | ||
1244 | */ | 1265 | */ |
1245 | CKEDITOR.editor.prototype.editable = function( element ) { | 1266 | CKEDITOR.editor.prototype.editable = function( element ) { |
1246 | var editable = this._.editable; | 1267 | var editable = this._.editable; |
@@ -1263,7 +1284,7 @@ | |||
1263 | CKEDITOR.on( 'instanceLoaded', function( evt ) { | 1284 | CKEDITOR.on( 'instanceLoaded', function( evt ) { |
1264 | var editor = evt.editor; | 1285 | var editor = evt.editor; |
1265 | 1286 | ||
1266 | // 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). |
1267 | editor.on( 'insertElement', function( evt ) { | 1288 | editor.on( 'insertElement', function( evt ) { |
1268 | var element = evt.data; | 1289 | var element = evt.data; |
1269 | 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' ) ) ) { |
@@ -1278,9 +1299,9 @@ | |||
1278 | if ( editor.readOnly ) | 1299 | if ( editor.readOnly ) |
1279 | return; | 1300 | return; |
1280 | 1301 | ||
1281 | // 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) |
1282 | var sel = editor.getSelection(); | 1303 | var sel = editor.getSelection(); |
1283 | // Do it only when selection is not locked. (#8222) | 1304 | // Do it only when selection is not locked. (http://dev.ckeditor.com/ticket/8222) |
1284 | if ( sel && !sel.isLocked ) { | 1305 | if ( sel && !sel.isLocked ) { |
1285 | var isDirty = editor.checkDirty(); | 1306 | var isDirty = editor.checkDirty(); |
1286 | 1307 | ||
@@ -1330,7 +1351,7 @@ | |||
1330 | } ); | 1351 | } ); |
1331 | } ); | 1352 | } ); |
1332 | 1353 | ||
1333 | // #9222: Show text cursor in Gecko. | 1354 | // http://dev.ckeditor.com/ticket/9222: Show text cursor in Gecko. |
1334 | // Show default cursor over control elements on all non-IEs. | 1355 | // Show default cursor over control elements on all non-IEs. |
1335 | 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}' ); |
1336 | 1357 | ||
@@ -1347,8 +1368,8 @@ | |||
1347 | // Matching an empty paragraph at the end of document. | 1368 | // Matching an empty paragraph at the end of document. |
1348 | 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; |
1349 | 1370 | ||
1350 | // 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 |
1351 | // non-exitable-block by padding extra br.(#3189) | 1372 | // non-exitable-block by padding extra br.(http://dev.ckeditor.com/ticket/3189) |
1352 | // Returns truly value when dom was changed, falsy otherwise. | 1373 | // Returns truly value when dom was changed, falsy otherwise. |
1353 | function fixDom( evt ) { | 1374 | function fixDom( evt ) { |
1354 | var editor = evt.editor, | 1375 | var editor = evt.editor, |
@@ -1369,7 +1390,7 @@ | |||
1369 | } | 1390 | } |
1370 | 1391 | ||
1371 | // 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 |
1372 | // to encapsulate inline contents inside editable. (#3657) | 1393 | // to encapsulate inline contents inside editable. (http://dev.ckeditor.com/ticket/3657) |
1373 | // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection | 1394 | // Don't autoparagraph if browser (namely - IE) incorrectly anchored selection |
1374 | // 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 |
1375 | // content of editable. | 1396 | // content of editable. |
@@ -1397,7 +1418,7 @@ | |||
1397 | 1418 | ||
1398 | selectionUpdateNeeded = 1; | 1419 | selectionUpdateNeeded = 1; |
1399 | 1420 | ||
1400 | // 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) |
1401 | evt.cancel(); | 1422 | evt.cancel(); |
1402 | } | 1423 | } |
1403 | } | 1424 | } |
@@ -1413,13 +1434,13 @@ | |||
1413 | if ( selection.isFake ) | 1434 | if ( selection.isFake ) |
1414 | return 0; | 1435 | return 0; |
1415 | 1436 | ||
1416 | // 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) |
1417 | var pathBlock = path.block || path.blockLimit, | 1438 | var pathBlock = path.block || path.blockLimit, |
1418 | lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); | 1439 | lastNode = pathBlock && pathBlock.getLast( isNotEmpty ); |
1419 | 1440 | ||
1420 | // Check some specialities of the current path block: | 1441 | // Check some specialities of the current path block: |
1421 | // 1. It is really displayed as block; (#7221) | 1442 | // 1. It is really displayed as block; (http://dev.ckeditor.com/ticket/7221) |
1422 | // 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) |
1423 | // 3. It doesn't have bogus br yet. | 1444 | // 3. It doesn't have bogus br yet. |
1424 | if ( | 1445 | if ( |
1425 | pathBlock && pathBlock.isBlockBoundary() && | 1446 | pathBlock && pathBlock.isBlockBoundary() && |
@@ -1556,7 +1577,7 @@ | |||
1556 | // Whether in given context (pathBlock, pathBlockLimit and editor settings) | 1577 | // Whether in given context (pathBlock, pathBlockLimit and editor settings) |
1557 | // editor should automatically wrap inline contents with blocks. | 1578 | // editor should automatically wrap inline contents with blocks. |
1558 | function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) { | 1579 | function shouldAutoParagraph( editor, pathBlock, pathBlockLimit ) { |
1559 | // 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). |
1560 | return editor.config.autoParagraph !== false && | 1581 | return editor.config.autoParagraph !== false && |
1561 | editor.activeEnterMode != CKEDITOR.ENTER_BR && | 1582 | editor.activeEnterMode != CKEDITOR.ENTER_BR && |
1562 | ( | 1583 | ( |
@@ -1623,7 +1644,7 @@ | |||
1623 | 1644 | ||
1624 | // Select range and stop execution. | 1645 | // Select range and stop execution. |
1625 | // If data has been totally emptied after the filtering, | 1646 | // If data has been totally emptied after the filtering, |
1626 | // any insertion is pointless (#10339). | 1647 | // any insertion is pointless (http://dev.ckeditor.com/ticket/10339). |
1627 | if ( data && processDataForInsertion( that, data ) ) { | 1648 | if ( data && processDataForInsertion( that, data ) ) { |
1628 | // DATA INSERTION | 1649 | // DATA INSERTION |
1629 | insertDataIntoRange( that ); | 1650 | insertDataIntoRange( that ); |
@@ -2008,7 +2029,7 @@ | |||
2008 | nodeName = node.getName(); | 2029 | nodeName = node.getName(); |
2009 | 2030 | ||
2010 | // Extract only the list items, when insertion happens | 2031 | // Extract only the list items, when insertion happens |
2011 | // 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) |
2012 | if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) { | 2033 | if ( insideOfList && nodeName in CKEDITOR.dtd.$list ) { |
2013 | nodesData = nodesData.concat( extractNodesData( node, that ) ); | 2034 | nodesData = nodesData.concat( extractNodesData( node, that ) ); |
2014 | continue; | 2035 | continue; |
@@ -2256,7 +2277,7 @@ | |||
2256 | } | 2277 | } |
2257 | 2278 | ||
2258 | // 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 |
2259 | // characters ($$, $&, etc.) are in data (#10367). | 2280 | // characters ($$, $&, etc.) are in data (http://dev.ckeditor.com/ticket/10367). |
2260 | return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data ); | 2281 | return wrapper.getOuterHtml().split( '{cke-peak}' ).join( data ); |
2261 | } | 2282 | } |
2262 | 2283 | ||
@@ -2517,7 +2538,7 @@ | |||
2517 | if ( ( bogus = startBlock.getBogus() ) ) | 2538 | if ( ( bogus = startBlock.getBogus() ) ) |
2518 | bogus.remove(); | 2539 | bogus.remove(); |
2519 | 2540 | ||
2520 | // Changing end container to element from text node (#12503). | 2541 | // Changing end container to element from text node (http://dev.ckeditor.com/ticket/12503). |
2521 | range.enlarge( CKEDITOR.ENLARGE_INLINE ); | 2542 | range.enlarge( CKEDITOR.ENLARGE_INLINE ); |
2522 | 2543 | ||
2523 | // Delete range contents. Do NOT merge. Merging is weird. | 2544 | // Delete range contents. Do NOT merge. Merging is weird. |
@@ -2540,7 +2561,7 @@ | |||
2540 | range = editor.getSelection().getRanges()[ 0 ]; | 2561 | range = editor.getSelection().getRanges()[ 0 ]; |
2541 | range.collapse( 1 ); | 2562 | range.collapse( 1 ); |
2542 | 2563 | ||
2543 | // Optimizing range containers from text nodes to elements (#12503). | 2564 | // Optimizing range containers from text nodes to elements (http://dev.ckeditor.com/ticket/12503). |
2544 | range.optimize(); | 2565 | range.optimize(); |
2545 | if ( range.startContainer.getHtml() === '' ) { | 2566 | if ( range.startContainer.getHtml() === '' ) { |
2546 | range.startContainer.appendBogus(); | 2567 | range.startContainer.appendBogus(); |
@@ -2762,7 +2783,7 @@ | |||
2762 | while ( ( next = endBookmark.getNext() ) ) { | 2783 | while ( ( next = endBookmark.getNext() ) ) { |
2763 | next.insertAfter( startBookmark ); | 2784 | next.insertAfter( startBookmark ); |
2764 | 2785 | ||
2765 | // 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). |
2766 | startBookmark = next; | 2787 | startBookmark = next; |
2767 | } | 2788 | } |
2768 | 2789 | ||
@@ -2913,7 +2934,7 @@ | |||
2913 | 2934 | ||
2914 | walker.guard = function( node, leaving ) { | 2935 | walker.guard = function( node, leaving ) { |
2915 | // Guard may be executed on some node boundaries multiple times, | 2936 | // Guard may be executed on some node boundaries multiple times, |
2916 | // 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) |
2917 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { | 2938 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { |
2918 | var key = 'visited_' + ( leaving ? 'out' : 'in' ); | 2939 | var key = 'visited_' + ( leaving ? 'out' : 'in' ); |
2919 | if ( node.getCustomData( key ) ) { | 2940 | if ( node.getCustomData( key ) ) { |
diff --git a/sources/core/editor.js b/sources/core/editor.js index 8dfce7f..640cec0 100644 --- a/sources/core/editor.js +++ b/sources/core/editor.js | |||
@@ -29,7 +29,7 @@ | |||
29 | // Call the CKEDITOR.event constructor to initialize this instance. | 29 | // Call the CKEDITOR.event constructor to initialize this instance. |
30 | CKEDITOR.event.call( this ); | 30 | CKEDITOR.event.call( this ); |
31 | 31 | ||
32 | // Make a clone of the config object, to avoid having it touched by our code. (#9636) | 32 | // Make a clone of the config object, to avoid having it touched by our code. (http://dev.ckeditor.com/ticket/9636) |
33 | instanceConfig = instanceConfig && CKEDITOR.tools.clone( instanceConfig ); | 33 | instanceConfig = instanceConfig && CKEDITOR.tools.clone( instanceConfig ); |
34 | 34 | ||
35 | // if editor is created off one page element. | 35 | // if editor is created off one page element. |
@@ -223,7 +223,7 @@ | |||
223 | function updateCommandsContext( editor, path, forceRefresh ) { | 223 | function updateCommandsContext( editor, path, forceRefresh ) { |
224 | // Commands cannot be refreshed without a path. In edge cases | 224 | // Commands cannot be refreshed without a path. In edge cases |
225 | // it may happen that there's no selection when this function is executed. | 225 | // it may happen that there's no selection when this function is executed. |
226 | // For example when active filter is changed in #10877. | 226 | // For example when active filter is changed in http://dev.ckeditor.com/ticket/10877. |
227 | if ( !path ) | 227 | if ( !path ) |
228 | return; | 228 | return; |
229 | 229 | ||
@@ -270,7 +270,7 @@ | |||
270 | } else { | 270 | } else { |
271 | // Load the custom configuration file. | 271 | // Load the custom configuration file. |
272 | // To resolve customConfig race conflicts, use scriptLoader#queue | 272 | // To resolve customConfig race conflicts, use scriptLoader#queue |
273 | // instead of scriptLoader#load (#6504). | 273 | // instead of scriptLoader#load (http://dev.ckeditor.com/ticket/6504). |
274 | CKEDITOR.scriptLoader.queue( customConfig, function() { | 274 | CKEDITOR.scriptLoader.queue( customConfig, function() { |
275 | // If the CKEDITOR.editorConfig function has been properly | 275 | // If the CKEDITOR.editorConfig function has been properly |
276 | // defined in the custom configuration file, cache it. | 276 | // defined in the custom configuration file, cache it. |
@@ -654,24 +654,49 @@ | |||
654 | return editor.blockless ? CKEDITOR.ENTER_BR : enterMode; | 654 | return editor.blockless ? CKEDITOR.ENTER_BR : enterMode; |
655 | } | 655 | } |
656 | 656 | ||
657 | // Create DocumentFragment from specified ranges. For now it handles only tables in Firefox | 657 | // Create DocumentFragment from specified ranges. For now it handles only tables |
658 | // and returns DocumentFragment from the 1. range for other cases. (#13884) | 658 | // and returns DocumentFragment from the 1. range for other cases. (http://dev.ckeditor.com/ticket/13884) |
659 | function createDocumentFragmentFromRanges( ranges, editable ) { | 659 | function createDocumentFragmentFromRanges( ranges, editable ) { |
660 | var docFragment = new CKEDITOR.dom.documentFragment(), | 660 | var docFragment = new CKEDITOR.dom.documentFragment(), |
661 | tableClone, | 661 | tableClone, |
662 | currentRow, | 662 | currentRow, |
663 | currentRowClone; | 663 | currentRowClone; |
664 | 664 | ||
665 | // We must handle two cases here: | ||
666 | // 1. <tr>[<td>Cell</td>]</tr> (IE9+, Edge, Chrome, Firefox) | ||
667 | // 2. <td>[Cell]</td> (IE8-, Safari) | ||
668 | function isSelectedCell( range ) { | ||
669 | var start = range.startContainer, | ||
670 | end = range.endContainer; | ||
671 | |||
672 | if ( start.is && ( start.is( 'tr' ) || | ||
673 | ( start.is( 'td' ) && start.equals( end ) && range.endOffset === start.getChildCount() ) ) ) { | ||
674 | return true; | ||
675 | } | ||
676 | |||
677 | return false; | ||
678 | } | ||
679 | |||
680 | function cloneCell( range ) { | ||
681 | var start = range.startContainer; | ||
682 | |||
683 | if ( start.is( 'tr' ) ) { | ||
684 | return range.cloneContents(); | ||
685 | } | ||
686 | |||
687 | return start.clone( true ); | ||
688 | } | ||
689 | |||
665 | for ( var i = 0; i < ranges.length; i++ ) { | 690 | for ( var i = 0; i < ranges.length; i++ ) { |
666 | var range = ranges[ i ], | 691 | var range = ranges[ i ], |
667 | container = range.startContainer; | 692 | container = range.startContainer.getAscendant( 'tr', true ); |
668 | 693 | ||
669 | if ( container.getName && container.getName() == 'tr' ) { | 694 | if ( isSelectedCell( range ) ) { |
670 | if ( !tableClone ) { | 695 | if ( !tableClone ) { |
671 | tableClone = container.getAscendant( 'table' ).clone(); | 696 | tableClone = container.getAscendant( 'table' ).clone(); |
672 | tableClone.append( container.getAscendant( 'tbody' ).clone() ); | 697 | tableClone.append( container.getAscendant( { thead: 1, tbody: 1, tfoot: 1 } ).clone() ); |
673 | docFragment.append( tableClone ); | 698 | docFragment.append( tableClone ); |
674 | tableClone = tableClone.findOne( 'tbody' ); | 699 | tableClone = tableClone.findOne( 'thead, tbody, tfoot' ); |
675 | } | 700 | } |
676 | 701 | ||
677 | if ( !( currentRow && currentRow.equals( container ) ) ) { | 702 | if ( !( currentRow && currentRow.equals( container ) ) ) { |
@@ -680,7 +705,7 @@ | |||
680 | tableClone.append( currentRowClone ); | 705 | tableClone.append( currentRowClone ); |
681 | } | 706 | } |
682 | 707 | ||
683 | currentRowClone.append( range.cloneContents() ); | 708 | currentRowClone.append( cloneCell( range ) ); |
684 | } else { | 709 | } else { |
685 | // If there was something else copied with table, | 710 | // If there was something else copied with table, |
686 | // append it to DocumentFragment. | 711 | // append it to DocumentFragment. |
@@ -717,7 +742,7 @@ | |||
717 | // This guarantees that commands added before first editor#mode | 742 | // This guarantees that commands added before first editor#mode |
718 | // aren't immediately updated, but waits for editor#mode and that | 743 | // aren't immediately updated, but waits for editor#mode and that |
719 | // commands added later are immediately refreshed, even when added | 744 | // commands added later are immediately refreshed, even when added |
720 | // before instanceReady. #10103, #10249 | 745 | // before instanceReady. http://dev.ckeditor.com/ticket/10103, http://dev.ckeditor.com/ticket/10249 |
721 | if ( this.mode ) | 746 | if ( this.mode ) |
722 | updateCommand( this, cmd ); | 747 | updateCommand( this, cmd ); |
723 | 748 | ||
@@ -761,7 +786,7 @@ | |||
761 | } ); | 786 | } ); |
762 | } | 787 | } |
763 | 788 | ||
764 | // Remove 'submit' events registered on form element before destroying.(#3988) | 789 | // Remove 'submit' events registered on form element before destroying.(http://dev.ckeditor.com/ticket/3988) |
765 | editor.on( 'destroy', function() { | 790 | editor.on( 'destroy', function() { |
766 | form.removeListener( 'submit', onSubmit ); | 791 | form.removeListener( 'submit', onSubmit ); |
767 | } ); | 792 | } ); |
@@ -771,7 +796,7 @@ | |||
771 | function onSubmit( evt ) { | 796 | function onSubmit( evt ) { |
772 | editor.updateElement(); | 797 | editor.updateElement(); |
773 | 798 | ||
774 | // #8031 If textarea had required attribute and editor is empty fire 'required' event and if | 799 | // http://dev.ckeditor.com/ticket/8031 If textarea had required attribute and editor is empty fire 'required' event and if |
775 | // it was cancelled, prevent submitting the form. | 800 | // it was cancelled, prevent submitting the form. |
776 | if ( editor._.required && !element.getValue() && editor.fire( 'required' ) === false ) { | 801 | if ( editor._.required && !element.getValue() && editor.fire( 'required' ) === false ) { |
777 | // When user press save button event (evt) is undefined (see save plugin). | 802 | // When user press save button event (evt) is undefined (see save plugin). |
@@ -860,10 +885,10 @@ | |||
860 | * | 885 | * |
861 | * editorInstance.execCommand( 'bold' ); | 886 | * editorInstance.execCommand( 'bold' ); |
862 | * | 887 | * |
863 | * @param {String} commandName The indentifier name of the command. | 888 | * @param {String} commandName The identifier name of the command. |
864 | * @param {Object} [data] The data to be passed to the command. | 889 | * @param {Object} [data] The data to be passed to the command. It defaults to |
865 | * @returns {Boolean} `true` if the command was executed | 890 | * an empty object starting from 4.7.0. |
866 | * successfully, otherwise `false`. | 891 | * @returns {Boolean} `true` if the command was executed successfully, `false` otherwise. |
867 | * @see CKEDITOR.editor#addCommand | 892 | * @see CKEDITOR.editor#addCommand |
868 | */ | 893 | */ |
869 | execCommand: function( commandName, data ) { | 894 | execCommand: function( commandName, data ) { |
@@ -871,7 +896,7 @@ | |||
871 | 896 | ||
872 | var eventData = { | 897 | var eventData = { |
873 | name: commandName, | 898 | name: commandName, |
874 | commandData: data, | 899 | commandData: data || {}, |
875 | command: command | 900 | command: command |
876 | }; | 901 | }; |
877 | 902 | ||
@@ -967,7 +992,7 @@ | |||
967 | } | 992 | } |
968 | else { | 993 | else { |
969 | // If we don't have a proper element, set data to an empty string, | 994 | // If we don't have a proper element, set data to an empty string, |
970 | // as this method is expected to return a string. (#13385) | 995 | // as this method is expected to return a string. (http://dev.ckeditor.com/ticket/13385) |
971 | data = ''; | 996 | data = ''; |
972 | } | 997 | } |
973 | } | 998 | } |
@@ -1069,7 +1094,7 @@ | |||
1069 | this.readOnly = isReadOnly; | 1094 | this.readOnly = isReadOnly; |
1070 | 1095 | ||
1071 | // Block or release BACKSPACE key according to current read-only | 1096 | // Block or release BACKSPACE key according to current read-only |
1072 | // state to prevent browser's history navigation (#9761). | 1097 | // state to prevent browser's history navigation (http://dev.ckeditor.com/ticket/9761). |
1073 | this.keystrokeHandler.blockedKeystrokes[ 8 ] = +isReadOnly; | 1098 | this.keystrokeHandler.blockedKeystrokes[ 8 ] = +isReadOnly; |
1074 | 1099 | ||
1075 | this.editable().setReadOnly( isReadOnly ); | 1100 | this.editable().setReadOnly( isReadOnly ); |
@@ -1198,17 +1223,20 @@ | |||
1198 | */ | 1223 | */ |
1199 | extractSelectedHtml: function( toString, removeEmptyBlock ) { | 1224 | extractSelectedHtml: function( toString, removeEmptyBlock ) { |
1200 | var editable = this.editable(), | 1225 | var editable = this.editable(), |
1201 | ranges = this.getSelection().getRanges(); | 1226 | ranges = this.getSelection().getRanges(), |
1227 | docFragment = new CKEDITOR.dom.documentFragment(), | ||
1228 | i; | ||
1202 | 1229 | ||
1203 | if ( !editable || ranges.length === 0 ) { | 1230 | if ( !editable || ranges.length === 0 ) { |
1204 | return null; | 1231 | return null; |
1205 | } | 1232 | } |
1206 | 1233 | ||
1207 | var range = ranges[ 0 ], | 1234 | for ( i = 0; i < ranges.length; i++ ) { |
1208 | docFragment = editable.extractHtmlFromRange( range, removeEmptyBlock ); | 1235 | docFragment.append( editable.extractHtmlFromRange( ranges[ i ], removeEmptyBlock ) ); |
1236 | } | ||
1209 | 1237 | ||
1210 | if ( !removeEmptyBlock ) { | 1238 | if ( !removeEmptyBlock ) { |
1211 | this.getSelection().selectRanges( [ range ] ); | 1239 | this.getSelection().selectRanges( [ ranges[ 0 ] ] ); |
1212 | } | 1240 | } |
1213 | 1241 | ||
1214 | return toString ? docFragment.getHtml() : docFragment; | 1242 | return toString ? docFragment.getHtml() : docFragment; |
@@ -1325,28 +1353,33 @@ | |||
1325 | 1353 | ||
1326 | /** | 1354 | /** |
1327 | * Returns the keystroke that is assigned to a specified {@link CKEDITOR.command}. If no keystroke is assigned, | 1355 | * Returns the keystroke that is assigned to a specified {@link CKEDITOR.command}. If no keystroke is assigned, |
1328 | * it returns null. | 1356 | * it returns `null`. |
1357 | * | ||
1358 | * Since version 4.7.0 this function also accepts a `command` parameter as a string. | ||
1329 | * | 1359 | * |
1330 | * @since 4.6.0 | 1360 | * @since 4.6.0 |
1331 | * @param {CKEDITOR.command} command | 1361 | * @param {CKEDITOR.command/String} command The {@link CKEDITOR.command} instance or a string with the command name. |
1332 | * @returns {Number} The keystroke assigned to the provided command or null if there is no keystroke. | 1362 | * @returns {Number/null} The keystroke assigned to the provided command or `null` if there is no keystroke. |
1333 | */ | 1363 | */ |
1334 | getCommandKeystroke: function( command ) { | 1364 | getCommandKeystroke: function( command ) { |
1335 | var commandName = command.name, | 1365 | var commandInstance = ( typeof command === 'string' ? this.getCommand( command ) : command ); |
1336 | keystrokes = this.keystrokeHandler.keystrokes, | ||
1337 | key; | ||
1338 | 1366 | ||
1339 | // Some commands have a fake keystroke - for example CUT/COPY/PASTE commands are handled natively. | 1367 | if ( commandInstance ) { |
1340 | if ( command.fakeKeystroke ) { | 1368 | var commandName = CKEDITOR.tools.object.findKey( this.commands, commandInstance ), |
1341 | return command.fakeKeystroke; | 1369 | keystrokes = this.keystrokeHandler.keystrokes, |
1342 | } | 1370 | key; |
1343 | 1371 | ||
1344 | for ( key in keystrokes ) { | 1372 | // Some commands have a fake keystroke - for example CUT/COPY/PASTE commands are handled natively. |
1345 | if ( keystrokes.hasOwnProperty( key ) && keystrokes[ key ] == commandName ) { | 1373 | if ( commandInstance.fakeKeystroke ) { |
1346 | return key; | 1374 | return commandInstance.fakeKeystroke; |
1347 | } | 1375 | } |
1348 | } | ||
1349 | 1376 | ||
1377 | for ( key in keystrokes ) { | ||
1378 | if ( keystrokes.hasOwnProperty( key ) && keystrokes[ key ] == commandName ) { | ||
1379 | return key; | ||
1380 | } | ||
1381 | } | ||
1382 | } | ||
1350 | return null; | 1383 | return null; |
1351 | }, | 1384 | }, |
1352 | 1385 | ||
@@ -1539,7 +1572,7 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3; | |||
1539 | * @member CKEDITOR.config | 1572 | * @member CKEDITOR.config |
1540 | */ | 1573 | */ |
1541 | 1574 | ||
1542 | /** | 1575 | /** |
1543 | * Customizes the {@link CKEDITOR.editor#title human-readable title} of this editor. This title is displayed in | 1576 | * Customizes the {@link CKEDITOR.editor#title human-readable title} of this editor. This title is displayed in |
1544 | * tooltips and impacts various [accessibility aspects](#!/guide/dev_a11y-section-announcing-the-editor-on-the-page), | 1577 | * tooltips and impacts various [accessibility aspects](#!/guide/dev_a11y-section-announcing-the-editor-on-the-page), |
1545 | * e.g. it is commonly used by screen readers for distinguishing editor instances and for navigation. | 1578 | * e.g. it is commonly used by screen readers for distinguishing editor instances and for navigation. |
diff --git a/sources/core/env.js b/sources/core/env.js index 43b608a..cbf089c 100644 --- a/sources/core/env.js +++ b/sources/core/env.js | |||
@@ -136,7 +136,7 @@ if ( !CKEDITOR.env ) { | |||
136 | var domain = document.domain, | 136 | var domain = document.domain, |
137 | hostname = window.location.hostname; | 137 | hostname = window.location.hostname; |
138 | 138 | ||
139 | return domain != hostname && domain != ( '[' + hostname + ']' ); // IPv6 IP support (#5434) | 139 | return domain != hostname && domain != ( '[' + hostname + ']' ); // IPv6 IP support (http://dev.ckeditor.com/ticket/5434) |
140 | }, | 140 | }, |
141 | 141 | ||
142 | /** | 142 | /** |
diff --git a/sources/core/filter.js b/sources/core/filter.js index db68530..3e64fc5 100644 --- a/sources/core/filter.js +++ b/sources/core/filter.js | |||
@@ -158,7 +158,8 @@ | |||
158 | }, | 158 | }, |
159 | // Object: element name => array of transformations groups. | 159 | // Object: element name => array of transformations groups. |
160 | transformations: {}, | 160 | transformations: {}, |
161 | cachedTests: {} | 161 | cachedTests: {}, |
162 | cachedChecks: {} | ||
162 | }; | 163 | }; |
163 | 164 | ||
164 | // Register filter instance. | 165 | // Register filter instance. |
@@ -299,7 +300,7 @@ | |||
299 | if ( el.attributes[ 'data-cke-filter' ] == 'off' ) | 300 | if ( el.attributes[ 'data-cke-filter' ] == 'off' ) |
300 | return false; | 301 | return false; |
301 | 302 | ||
302 | // (#10260) Don't touch elements like spans with data-cke-* attribute since they're | 303 | // (http://dev.ckeditor.com/ticket/10260) Don't touch elements like spans with data-cke-* attribute since they're |
303 | // responsible e.g. for placing markers, bookmarks, odds and stuff. | 304 | // responsible e.g. for placing markers, bookmarks, odds and stuff. |
304 | // We love 'em and we don't wanna lose anything during the filtering. | 305 | // We love 'em and we don't wanna lose anything during the filtering. |
305 | // '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible. | 306 | // '|' is to avoid tricky joints like data-="foo" + cke-="bar". Yes, they're possible. |
@@ -346,7 +347,7 @@ | |||
346 | if ( !element.parent ) | 347 | if ( !element.parent ) |
347 | continue; | 348 | continue; |
348 | 349 | ||
349 | // Handle custom elements as inline elements (#12683). | 350 | // Handle custom elements as inline elements (http://dev.ckeditor.com/ticket/12683). |
350 | parentDtd = DTD[ element.parent.name ] || DTD.span; | 351 | parentDtd = DTD[ element.parent.name ] || DTD.span; |
351 | 352 | ||
352 | switch ( check.check ) { | 353 | switch ( check.check ) { |
@@ -806,6 +807,32 @@ | |||
806 | } )(), | 807 | } )(), |
807 | 808 | ||
808 | /** | 809 | /** |
810 | * Returns a clone of this filter instance. | ||
811 | * | ||
812 | * @since 4.7.3 | ||
813 | * @returns {CKEDITOR.filter} | ||
814 | */ | ||
815 | clone: function() { | ||
816 | var ret = new CKEDITOR.filter(), | ||
817 | clone = CKEDITOR.tools.clone; | ||
818 | |||
819 | // Cloning allowed content related things. | ||
820 | ret.allowedContent = clone( this.allowedContent ); | ||
821 | ret._.allowedRules = clone( this._.allowedRules ); | ||
822 | |||
823 | // Disallowed content rules. | ||
824 | ret.disallowedContent = clone( this.disallowedContent ); | ||
825 | ret._.disallowedRules = clone( this._.disallowedRules ); | ||
826 | |||
827 | ret._.transformations = clone( this._.transformations ); | ||
828 | |||
829 | ret.disabled = this.disabled; | ||
830 | ret.editor = this.editor; | ||
831 | |||
832 | return ret; | ||
833 | }, | ||
834 | |||
835 | /** | ||
809 | * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object. | 836 | * Destroys the filter instance and removes it from the global {@link CKEDITOR.filter#instances} object. |
810 | * | 837 | * |
811 | * @since 4.4.5 | 838 | * @since 4.4.5 |
@@ -2201,7 +2228,7 @@ | |||
2201 | var widths = element.styles.margin.match( /(\-?[\.\d]+\w+)/g ) || [ '0px' ]; | 2228 | var widths = element.styles.margin.match( /(\-?[\.\d]+\w+)/g ) || [ '0px' ]; |
2202 | switch ( widths.length ) { | 2229 | switch ( widths.length ) { |
2203 | case 1: | 2230 | case 1: |
2204 | element.styles.margin = widths[0]; | 2231 | mapStyles( [ 0, 0, 0, 0 ] ); |
2205 | break; | 2232 | break; |
2206 | case 2: | 2233 | case 2: |
2207 | mapStyles( [ 0, 1, 0, 1 ] ); | 2234 | mapStyles( [ 0, 1, 0, 1 ] ); |
@@ -2291,7 +2318,7 @@ | |||
2291 | * * {@link CKEDITOR.filter.allowedContentRules} – defined rules will be added | 2318 | * * {@link CKEDITOR.filter.allowedContentRules} – defined rules will be added |
2292 | * to the {@link CKEDITOR.editor#filter}. | 2319 | * to the {@link CKEDITOR.editor#filter}. |
2293 | * * `true` – will disable the filter (data will not be filtered, | 2320 | * * `true` – will disable the filter (data will not be filtered, |
2294 | * all features will be activated). | 2321 | * all features will be activated). Reading [security best practices](#!/guide/dev_best_practices) before setting `true` is recommended. |
2295 | * * default – the filter will be configured by loaded features | 2322 | * * default – the filter will be configured by loaded features |
2296 | * (toolbar items, commands, etc.). | 2323 | * (toolbar items, commands, etc.). |
2297 | * | 2324 | * |
diff --git a/sources/core/focusmanager.js b/sources/core/focusmanager.js index 6fc9969..45c3137 100644 --- a/sources/core/focusmanager.js +++ b/sources/core/focusmanager.js | |||
@@ -147,34 +147,28 @@ | |||
147 | * @member CKEDITOR.focusManager | 147 | * @member CKEDITOR.focusManager |
148 | */ | 148 | */ |
149 | blur: function( noDelay ) { | 149 | blur: function( noDelay ) { |
150 | if ( this._.locked ) | 150 | if ( this._.locked ) { |
151 | return; | 151 | return; |
152 | } | ||
152 | 153 | ||
153 | function doBlur() { | 154 | function doBlur() { |
154 | var editor = this._.editor; | ||
155 | |||
156 | if ( this.hasFocus ) { | 155 | if ( this.hasFocus ) { |
157 | this.hasFocus = false; | 156 | this.hasFocus = false; |
158 | 157 | ||
159 | // Blink browsers leave selection in `[contenteditable=true]` | ||
160 | // when it's blurred and it's neccessary to remove it manually for inline editor. (#13446) | ||
161 | if ( CKEDITOR.env.chrome && editor.editable().isInline() ) { | ||
162 | editor.window.$.getSelection().removeAllRanges(); | ||
163 | } | ||
164 | |||
165 | var ct = this._.editor.container; | 158 | var ct = this._.editor.container; |
166 | ct && ct.removeClass( 'cke_focus' ); | 159 | ct && ct.removeClass( 'cke_focus' ); |
167 | this._.editor.fire( 'blur' ); | 160 | this._.editor.fire( 'blur' ); |
168 | } | 161 | } |
169 | } | 162 | } |
170 | 163 | ||
171 | if ( this._.timer ) | 164 | if ( this._.timer ) { |
172 | clearTimeout( this._.timer ); | 165 | clearTimeout( this._.timer ); |
166 | } | ||
173 | 167 | ||
174 | var delay = CKEDITOR.focusManager._.blurDelay; | 168 | var delay = CKEDITOR.focusManager._.blurDelay; |
175 | if ( noDelay || !delay ) | 169 | if ( noDelay || !delay ) { |
176 | doBlur.call( this ); | 170 | doBlur.call( this ); |
177 | else { | 171 | } else { |
178 | this._.timer = CKEDITOR.tools.setTimeout( function() { | 172 | this._.timer = CKEDITOR.tools.setTimeout( function() { |
179 | delete this._.timer; | 173 | delete this._.timer; |
180 | doBlur.call( this ); | 174 | doBlur.call( this ); |
diff --git a/sources/core/htmldataprocessor.js b/sources/core/htmldataprocessor.js index 56764be..79e996b 100644 --- a/sources/core/htmldataprocessor.js +++ b/sources/core/htmldataprocessor.js | |||
@@ -56,7 +56,7 @@ | |||
56 | // it up and apply the filter. | 56 | // it up and apply the filter. |
57 | data = protectSource( data, editor ); | 57 | data = protectSource( data, editor ); |
58 | 58 | ||
59 | // Protect content of textareas. (#9995) | 59 | // Protect content of textareas. (http://dev.ckeditor.com/ticket/9995) |
60 | // Do this before protecting attributes to avoid breaking: | 60 | // Do this before protecting attributes to avoid breaking: |
61 | // <textarea><img src="..." /></textarea> | 61 | // <textarea><img src="..." /></textarea> |
62 | data = protectElements( data, protectTextareaRegex ); | 62 | data = protectElements( data, protectTextareaRegex ); |
@@ -67,23 +67,23 @@ | |||
67 | data = protectAttributes( data ); | 67 | data = protectAttributes( data ); |
68 | 68 | ||
69 | // Protect elements than can't be set inside a DIV. E.g. IE removes | 69 | // Protect elements than can't be set inside a DIV. E.g. IE removes |
70 | // style tags from innerHTML. (#3710) | 70 | // style tags from innerHTML. (http://dev.ckeditor.com/ticket/3710) |
71 | data = protectElements( data, protectElementsRegex ); | 71 | data = protectElements( data, protectElementsRegex ); |
72 | 72 | ||
73 | // Certain elements has problem to go through DOM operation, protect | 73 | // Certain elements has problem to go through DOM operation, protect |
74 | // them by prefixing 'cke' namespace. (#3591) | 74 | // them by prefixing 'cke' namespace. (http://dev.ckeditor.com/ticket/3591) |
75 | data = protectElementsNames( data ); | 75 | data = protectElementsNames( data ); |
76 | 76 | ||
77 | // All none-IE browsers ignore self-closed custom elements, | 77 | // All none-IE browsers ignore self-closed custom elements, |
78 | // protecting them into open-close. (#3591) | 78 | // protecting them into open-close. (http://dev.ckeditor.com/ticket/3591) |
79 | data = protectSelfClosingElements( data ); | 79 | data = protectSelfClosingElements( data ); |
80 | 80 | ||
81 | // Compensate one leading line break after <pre> open as browsers | 81 | // Compensate one leading line break after <pre> open as browsers |
82 | // eat it up. (#5789) | 82 | // eat it up. (http://dev.ckeditor.com/ticket/5789) |
83 | data = protectPreFormatted( data ); | 83 | data = protectPreFormatted( data ); |
84 | 84 | ||
85 | // There are attributes which may execute JavaScript code inside fixBin. | 85 | // There are attributes which may execute JavaScript code inside fixBin. |
86 | // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (#10) | 86 | // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (http://dev.ckeditor.com/ticket/10) |
87 | data = protectInsecureAttributes( data ); | 87 | data = protectInsecureAttributes( data ); |
88 | 88 | ||
89 | var fixBin = evtData.context || editor.editable().getName(), | 89 | var fixBin = evtData.context || editor.editable().getName(), |
@@ -99,7 +99,7 @@ | |||
99 | // Call the browser to help us fixing a possibly invalid HTML | 99 | // Call the browser to help us fixing a possibly invalid HTML |
100 | // structure. | 100 | // structure. |
101 | var el = editor.document.createElement( fixBin ); | 101 | var el = editor.document.createElement( fixBin ); |
102 | // Add fake character to workaround IE comments bug. (#3801) | 102 | // Add fake character to workaround IE comments bug. (http://dev.ckeditor.com/ticket/3801) |
103 | el.setHtml( 'a' + data ); | 103 | el.setHtml( 'a' + data ); |
104 | data = el.getHtml().substr( 1 ); | 104 | data = el.getHtml().substr( 1 ); |
105 | 105 | ||
@@ -128,7 +128,7 @@ | |||
128 | data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag ); | 128 | data = CKEDITOR.htmlParser.fragment.fromHtml( data, evtData.context, fixBodyTag ); |
129 | 129 | ||
130 | // The empty root element needs to be fixed by adding 'p' or 'div' into it. | 130 | // The empty root element needs to be fixed by adding 'p' or 'div' into it. |
131 | // This avoids the need to create that element on the first focus (#12630). | 131 | // This avoids the need to create that element on the first focus (http://dev.ckeditor.com/ticket/12630). |
132 | if ( fixBodyTag ) { | 132 | if ( fixBodyTag ) { |
133 | fixEmptyRoot( data, fixBodyTag ); | 133 | fixEmptyRoot( data, fixBodyTag ); |
134 | } | 134 | } |
@@ -163,7 +163,7 @@ | |||
163 | editor.on( 'toDataFormat', function( evt ) { | 163 | editor.on( 'toDataFormat', function( evt ) { |
164 | var data = evt.data.dataValue; | 164 | var data = evt.data.dataValue; |
165 | 165 | ||
166 | // #10854 - we need to strip leading blockless <br> which FF adds | 166 | // http://dev.ckeditor.com/ticket/10854 - we need to strip leading blockless <br> which FF adds |
167 | // automatically when editable contains only non-editable content. | 167 | // automatically when editable contains only non-editable content. |
168 | // We do that for every browser (so it's a constant behavior) and | 168 | // We do that for every browser (so it's a constant behavior) and |
169 | // not in BR mode, in which chance of valid leading blockless <br> is higher. | 169 | // not in BR mode, in which chance of valid leading blockless <br> is higher. |
@@ -192,7 +192,7 @@ | |||
192 | data.writeChildrenHtml( writer ); | 192 | data.writeChildrenHtml( writer ); |
193 | data = writer.getHtml( true ); | 193 | data = writer.getHtml( true ); |
194 | 194 | ||
195 | // Restore those non-HTML protected source. (#4475,#4880) | 195 | // Restore those non-HTML protected source. (http://dev.ckeditor.com/ticket/4475,http://dev.ckeditor.com/ticket/4880) |
196 | data = unprotectRealComments( data ); | 196 | data = unprotectRealComments( data ); |
197 | data = unprotectSource( data, editor ); | 197 | data = unprotectSource( data, editor ); |
198 | 198 | ||
@@ -448,7 +448,7 @@ | |||
448 | return false; | 448 | return false; |
449 | 449 | ||
450 | // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg; | 450 | // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg; |
451 | // 2. For the rest, at least table cell and list item need no filler space. (#6248) | 451 | // 2. For the rest, at least table cell and list item need no filler space. (http://dev.ckeditor.com/ticket/6248) |
452 | if ( !isOutput && !CKEDITOR.env.needsBrFiller && | 452 | if ( !isOutput && !CKEDITOR.env.needsBrFiller && |
453 | ( document.documentMode > 7 || | 453 | ( document.documentMode > 7 || |
454 | block.name in CKEDITOR.dtd.tr || | 454 | block.name in CKEDITOR.dtd.tr || |
@@ -484,7 +484,7 @@ | |||
484 | } | 484 | } |
485 | 485 | ||
486 | // Regex to scan for at the end of blocks, which are actually placeholders. | 486 | // Regex to scan for at the end of blocks, which are actually placeholders. |
487 | // Safari transforms the to \xa0. (#4172) | 487 | // Safari transforms the to \xa0. (http://dev.ckeditor.com/ticket/4172) |
488 | var tailNbspRegex = /(?: |\xa0)$/; | 488 | var tailNbspRegex = /(?: |\xa0)$/; |
489 | 489 | ||
490 | var protectedSourceMarker = '{cke_protected}'; | 490 | var protectedSourceMarker = '{cke_protected}'; |
@@ -563,18 +563,35 @@ | |||
563 | // active in the editing area (IE|WebKit). | 563 | // active in the editing area (IE|WebKit). |
564 | [ ( /^on/ ), 'data-cke-pa-on' ], | 564 | [ ( /^on/ ), 'data-cke-pa-on' ], |
565 | 565 | ||
566 | // Prevent iframe's srcdoc attribute from being evaluated in the editable. | ||
567 | [ ( /^srcdoc/ ), 'data-cke-pa-srcdoc' ], | ||
568 | |||
566 | // Don't let some old expando enter editor. Concerns only IE8, | 569 | // Don't let some old expando enter editor. Concerns only IE8, |
567 | // but for consistency remove on all browsers. | 570 | // but for consistency remove on all browsers. |
568 | [ ( /^data-cke-expando$/ ), '' ] | 571 | [ ( /^data-cke-expando$/ ), '' ] |
569 | ] | 572 | ], |
573 | |||
574 | elements: { | ||
575 | // Prevent iframe's src attribute with javascript code or data protocol from being evaluated in the editable. | ||
576 | iframe: function( element ) { | ||
577 | if ( element.attributes && element.attributes.src ) { | ||
578 | |||
579 | var src = element.attributes.src.toLowerCase().replace( /[^a-z]/gi, '' ); | ||
580 | if ( src.indexOf( 'javascript' ) === 0 || src.indexOf( 'data' ) === 0 ) { | ||
581 | element.attributes[ 'data-cke-pa-src' ] = element.attributes.src; | ||
582 | delete element.attributes.src; | ||
583 | } | ||
584 | } | ||
585 | } | ||
586 | } | ||
570 | }; | 587 | }; |
571 | 588 | ||
572 | // Disable form elements editing mode provided by some browsers. (#5746) | 589 | // Disable form elements editing mode provided by some browsers. (http://dev.ckeditor.com/ticket/5746) |
573 | function protectReadOnly( element ) { | 590 | function protectReadOnly( element ) { |
574 | var attrs = element.attributes; | 591 | var attrs = element.attributes; |
575 | 592 | ||
576 | // We should flag that the element was locked by our code so | 593 | // We should flag that the element was locked by our code so |
577 | // it'll be editable by the editor functions (#6046). | 594 | // it'll be editable by the editor functions (http://dev.ckeditor.com/ticket/6046). |
578 | if ( attrs.contenteditable != 'false' ) | 595 | if ( attrs.contenteditable != 'false' ) |
579 | attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1; | 596 | attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1; |
580 | 597 | ||
@@ -602,7 +619,7 @@ | |||
602 | } | 619 | } |
603 | }, | 620 | }, |
604 | 621 | ||
605 | // Remove empty link but not empty anchor. (#3829, #13516) | 622 | // Remove empty link but not empty anchor. (http://dev.ckeditor.com/ticket/3829, http://dev.ckeditor.com/ticket/13516) |
606 | a: function( element ) { | 623 | a: function( element ) { |
607 | var attrs = element.attributes; | 624 | var attrs = element.attributes; |
608 | 625 | ||
@@ -641,7 +658,7 @@ | |||
641 | if ( attribs[ 'data-cke-temp' ] ) | 658 | if ( attribs[ 'data-cke-temp' ] ) |
642 | return false; | 659 | return false; |
643 | 660 | ||
644 | // Remove duplicated attributes - #3789. | 661 | // Remove duplicated attributes - http://dev.ckeditor.com/ticket/3789. |
645 | var attributeNames = [ 'name', 'href', 'src' ], | 662 | var attributeNames = [ 'name', 'href', 'src' ], |
646 | savedAttributeName; | 663 | savedAttributeName; |
647 | for ( var i = 0; i < attributeNames.length; i++ ) { | 664 | for ( var i = 0; i < attributeNames.length; i++ ) { |
@@ -653,7 +670,7 @@ | |||
653 | return element; | 670 | return element; |
654 | }, | 671 | }, |
655 | 672 | ||
656 | // The contents of table should be in correct order (#4809). | 673 | // The contents of table should be in correct order (http://dev.ckeditor.com/ticket/4809). |
657 | table: function( element ) { | 674 | table: function( element ) { |
658 | // Clone the array as it would become empty during the sort call. | 675 | // Clone the array as it would become empty during the sort call. |
659 | var children = element.children.slice( 0 ); | 676 | var children = element.children.slice( 0 ); |
@@ -712,7 +729,7 @@ | |||
712 | title: function( element ) { | 729 | title: function( element ) { |
713 | var titleText = element.children[ 0 ]; | 730 | var titleText = element.children[ 0 ]; |
714 | 731 | ||
715 | // Append text-node to title tag if not present (i.e. non-IEs) (#9882). | 732 | // Append text-node to title tag if not present (i.e. non-IEs) (http://dev.ckeditor.com/ticket/9882). |
716 | !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() ); | 733 | !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() ); |
717 | 734 | ||
718 | // Transfer data-saved title to title tag. | 735 | // Transfer data-saved title to title tag. |
@@ -733,7 +750,7 @@ | |||
733 | 750 | ||
734 | if ( CKEDITOR.env.ie ) { | 751 | if ( CKEDITOR.env.ie ) { |
735 | // IE outputs style attribute in capital letters. We should convert | 752 | // IE outputs style attribute in capital letters. We should convert |
736 | // them back to lower case, while not hurting the values (#5930) | 753 | // them back to lower case, while not hurting the values (http://dev.ckeditor.com/ticket/5930) |
737 | defaultHtmlFilterRulesForAll.attributes.style = function( value ) { | 754 | defaultHtmlFilterRulesForAll.attributes.style = function( value ) { |
738 | return value.replace( /(^|;)([^\:]+)/g, function( match ) { | 755 | return value.replace( /(^|;)([^\:]+)/g, function( match ) { |
739 | return match.toLowerCase(); | 756 | return match.toLowerCase(); |
@@ -741,7 +758,7 @@ | |||
741 | }; | 758 | }; |
742 | } | 759 | } |
743 | 760 | ||
744 | // Disable form elements editing mode provided by some browsers. (#5746) | 761 | // Disable form elements editing mode provided by some browsers. (http://dev.ckeditor.com/ticket/5746) |
745 | function unprotectReadyOnly( element ) { | 762 | function unprotectReadyOnly( element ) { |
746 | var attrs = element.attributes; | 763 | var attrs = element.attributes; |
747 | switch ( attrs[ 'data-cke-editable' ] ) { | 764 | switch ( attrs[ 'data-cke-editable' ] ) { |
@@ -773,7 +790,7 @@ | |||
773 | // | 790 | // |
774 | // 'data-x' => '<a href="X"' | 791 | // 'data-x' => '<a href="X"' |
775 | // | 792 | // |
776 | // which, can be easily filtered out (#11508). | 793 | // which, can be easily filtered out (http://dev.ckeditor.com/ticket/11508). |
777 | protectAttributeRegex = /([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi, | 794 | protectAttributeRegex = /([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi, |
778 | protectAttributeNameRegex = /^(href|src|name)$/i; | 795 | protectAttributeNameRegex = /^(href|src|name)$/i; |
779 | 796 | ||
@@ -790,8 +807,8 @@ | |||
790 | function protectAttributes( html ) { | 807 | function protectAttributes( html ) { |
791 | return html.replace( protectElementRegex, function( element, tag, attributes ) { | 808 | return html.replace( protectElementRegex, function( element, tag, attributes ) { |
792 | return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) { | 809 | return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) { |
793 | // Avoid corrupting the inline event attributes (#7243). | 810 | // Avoid corrupting the inline event attributes (http://dev.ckeditor.com/ticket/7243). |
794 | // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218) | 811 | // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (http://dev.ckeditor.com/ticket/5218) |
795 | if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 ) | 812 | if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 ) |
796 | return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr; | 813 | return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr; |
797 | 814 | ||
@@ -880,7 +897,7 @@ | |||
880 | // <noscript> tags (get lost in IE and messed up in FF). | 897 | // <noscript> tags (get lost in IE and messed up in FF). |
881 | /<noscript[\s\S]*?<\/noscript>/gi, | 898 | /<noscript[\s\S]*?<\/noscript>/gi, |
882 | 899 | ||
883 | // Avoid meta tags being stripped (#8117). | 900 | // Avoid meta tags being stripped (http://dev.ckeditor.com/ticket/8117). |
884 | /<meta[\s\S]*?\/?>/gi | 901 | /<meta[\s\S]*?\/?>/gi |
885 | ].concat( protectRegexes ); | 902 | ].concat( protectRegexes ); |
886 | 903 | ||
@@ -894,7 +911,7 @@ | |||
894 | 911 | ||
895 | for ( var i = 0; i < regexes.length; i++ ) { | 912 | for ( var i = 0; i < regexes.length; i++ ) { |
896 | data = data.replace( regexes[ i ], function( match ) { | 913 | data = data.replace( regexes[ i ], function( match ) { |
897 | match = match.replace( tempRegex, // There could be protected source inside another one. (#3869). | 914 | match = match.replace( tempRegex, // There could be protected source inside another one. (http://dev.ckeditor.com/ticket/3869). |
898 | function( $, isComment, id ) { | 915 | function( $, isComment, id ) { |
899 | return protectedHtml[ id ]; | 916 | return protectedHtml[ id ]; |
900 | } ); | 917 | } ); |
@@ -912,7 +929,7 @@ | |||
912 | 929 | ||
913 | // Different protection pattern is used for those that | 930 | // Different protection pattern is used for those that |
914 | // live in attributes to avoid from being HTML encoded. | 931 | // live in attributes to avoid from being HTML encoded. |
915 | // Why so serious? See #9205, #8216, #7805, #11754, #11846. | 932 | // Why so serious? See http://dev.ckeditor.com/ticket/9205, http://dev.ckeditor.com/ticket/8216, http://dev.ckeditor.com/ticket/7805, http://dev.ckeditor.com/ticket/11754, http://dev.ckeditor.com/ticket/11846. |
916 | data = data.replace( /<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g, function( match ) { | 933 | data = data.replace( /<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g, function( match ) { |
917 | return match.replace( /<!--\{cke_protected\}([^>]*)-->/g, function( match, data ) { | 934 | return match.replace( /<!--\{cke_protected\}([^>]*)-->/g, function( match, data ) { |
918 | store[ store.id ] = decodeURIComponent( data ); | 935 | store[ store.id ] = decodeURIComponent( data ); |
@@ -922,7 +939,7 @@ | |||
922 | 939 | ||
923 | // This RegExp searches for innerText in all the title/iframe/textarea elements. | 940 | // This RegExp searches for innerText in all the title/iframe/textarea elements. |
924 | // This is because browser doesn't allow HTML in these elements, that's why we can't | 941 | // This is because browser doesn't allow HTML in these elements, that's why we can't |
925 | // nest comments in there. (#11223) | 942 | // nest comments in there. (http://dev.ckeditor.com/ticket/11223) |
926 | data = data.replace( /<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g, function( match, tagName, tagAttributes, innerText ) { | 943 | data = data.replace( /<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g, function( match, tagName, tagAttributes, innerText ) { |
927 | return '<' + tagName + tagAttributes + '>' + unprotectSource( unprotectRealComments( innerText ), editor ) + '</' + tagName + '>'; | 944 | return '<' + tagName + tagAttributes + '>' + unprotectSource( unprotectRealComments( innerText ), editor ) + '</' + tagName + '>'; |
928 | } ); | 945 | } ); |
@@ -971,7 +988,7 @@ | |||
971 | * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}. | 988 | * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}. |
972 | * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter} | 989 | * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter} |
973 | * is not applied yet. | 990 | * is not applied yet. |
974 | * * 6: Data is filtered with the {CKEDITOR.filter content filter}. | 991 | * * 6: Data is filtered with the {@link CKEDITOR.filter content filter}. |
975 | * * 10: Data is processed with {@link CKEDITOR.htmlDataProcessor#dataFilter}. | 992 | * * 10: Data is processed with {@link CKEDITOR.htmlDataProcessor#dataFilter}. |
976 | * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter} | 993 | * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter} |
977 | * has already been applied. | 994 | * has already been applied. |
diff --git a/sources/core/htmlparser.js b/sources/core/htmlparser.js index 8c30992..c95257c 100644 --- a/sources/core/htmlparser.js +++ b/sources/core/htmlparser.js | |||
@@ -163,7 +163,7 @@ CKEDITOR.htmlParser = function() { | |||
163 | tagName = tagName.toLowerCase(); | 163 | tagName = tagName.toLowerCase(); |
164 | 164 | ||
165 | // There are some tag names that can break things, so let's | 165 | // There are some tag names that can break things, so let's |
166 | // simply ignore them when parsing. (#5224) | 166 | // simply ignore them when parsing. (http://dev.ckeditor.com/ticket/5224) |
167 | if ( /="/.test( tagName ) ) | 167 | if ( /="/.test( tagName ) ) |
168 | continue; | 168 | continue; |
169 | 169 | ||
diff --git a/sources/core/htmlparser/basicwriter.js b/sources/core/htmlparser/basicwriter.js index 529fbf1..61447f0 100644 --- a/sources/core/htmlparser/basicwriter.js +++ b/sources/core/htmlparser/basicwriter.js | |||
@@ -67,7 +67,7 @@ CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass( { | |||
67 | * @param {String} attValue The attribute value. | 67 | * @param {String} attValue The attribute value. |
68 | */ | 68 | */ |
69 | attribute: function( attName, attValue ) { | 69 | attribute: function( attName, attValue ) { |
70 | // Browsers don't always escape special character in attribute values. (#4683, #4719). | 70 | // Browsers don't always escape special character in attribute values. (http://dev.ckeditor.com/ticket/4683, http://dev.ckeditor.com/ticket/4719). |
71 | if ( typeof attValue == 'string' ) | 71 | if ( typeof attValue == 'string' ) |
72 | attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); | 72 | attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); |
73 | 73 | ||
diff --git a/sources/core/htmlparser/element.js b/sources/core/htmlparser/element.js index 0ed750e..224d3e6 100644 --- a/sources/core/htmlparser/element.js +++ b/sources/core/htmlparser/element.js | |||
@@ -35,7 +35,7 @@ CKEDITOR.htmlParser.element = function( name, attributes ) { | |||
35 | */ | 35 | */ |
36 | this.children = []; | 36 | this.children = []; |
37 | 37 | ||
38 | // Reveal the real semantic of our internal custom tag name (#6639), | 38 | // Reveal the real semantic of our internal custom tag name (http://dev.ckeditor.com/ticket/6639), |
39 | // when resolving whether it's block like. | 39 | // when resolving whether it's block like. |
40 | var realName = name || '', | 40 | var realName = name || '', |
41 | prefixed = realName.match( /^cke:(.*)/ ); | 41 | prefixed = realName.match( /^cke:(.*)/ ); |
@@ -543,8 +543,8 @@ CKEDITOR.htmlParser.cssStyle = function() { | |||
543 | 543 | ||
544 | if ( !ctx.nonEditable && this.attributes.contenteditable == 'false' ) | 544 | if ( !ctx.nonEditable && this.attributes.contenteditable == 'false' ) |
545 | changes.push( 'nonEditable', true ); | 545 | changes.push( 'nonEditable', true ); |
546 | // A context to be given nestedEditable must be nonEditable first (by inheritance) (#11372, #11698). | 546 | // A context to be given nestedEditable must be nonEditable first (by inheritance) (http://dev.ckeditor.com/ticket/11372, http://dev.ckeditor.com/ticket/11698). |
547 | // Special case: #11504 - filter starts on <body contenteditable=true>, | 547 | // Special case: http://dev.ckeditor.com/ticket/11504 - filter starts on <body contenteditable=true>, |
548 | // so ctx.nonEditable has not been yet set to true. | 548 | // so ctx.nonEditable has not been yet set to true. |
549 | else if ( ctx.nonEditable && !ctx.nestedEditable && this.attributes.contenteditable == 'true' ) | 549 | else if ( ctx.nonEditable && !ctx.nestedEditable && this.attributes.contenteditable == 'true' ) |
550 | changes.push( 'nestedEditable', true ); | 550 | changes.push( 'nestedEditable', true ); |
diff --git a/sources/core/htmlparser/fragment.js b/sources/core/htmlparser/fragment.js index f696a12..7ef915c 100644 --- a/sources/core/htmlparser/fragment.js +++ b/sources/core/htmlparser/fragment.js | |||
@@ -61,7 +61,7 @@ CKEDITOR.htmlParser.fragment = function() { | |||
61 | if ( node.attributes[ 'data-cke-survive' ] ) | 61 | if ( node.attributes[ 'data-cke-survive' ] ) |
62 | return false; | 62 | return false; |
63 | 63 | ||
64 | // Empty link is to be removed when empty but not anchor. (#7894) | 64 | // Empty link is to be removed when empty but not anchor. (http://dev.ckeditor.com/ticket/7894) |
65 | return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ]; | 65 | return node.name == 'a' && node.attributes.href || CKEDITOR.dtd.$removeEmpty[ node.name ]; |
66 | } | 66 | } |
67 | 67 | ||
@@ -130,7 +130,7 @@ CKEDITOR.htmlParser.fragment = function() { | |||
130 | i--; | 130 | i--; |
131 | } else { | 131 | } else { |
132 | // Some element of the same type cannot be nested, flat them, | 132 | // Some element of the same type cannot be nested, flat them, |
133 | // e.g. <a href="#">foo<a href="#">bar</a></a>. (#7894) | 133 | // e.g. <a href="#">foo<a href="#">bar</a></a>. (http://dev.ckeditor.com/ticket/7894) |
134 | if ( pendingName == currentNode.name ) | 134 | if ( pendingName == currentNode.name ) |
135 | addElement( currentNode, currentNode.parent, 1 ), i--; | 135 | addElement( currentNode, currentNode.parent, 1 ), i--; |
136 | } | 136 | } |
@@ -143,7 +143,7 @@ CKEDITOR.htmlParser.fragment = function() { | |||
143 | addElement( pendingBRs.shift(), currentNode ); | 143 | addElement( pendingBRs.shift(), currentNode ); |
144 | } | 144 | } |
145 | 145 | ||
146 | // Rtrim empty spaces on block end boundary. (#3585) | 146 | // Rtrim empty spaces on block end boundary. (http://dev.ckeditor.com/ticket/3585) |
147 | function removeTailWhitespace( element ) { | 147 | function removeTailWhitespace( element ) { |
148 | if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) { | 148 | if ( element._.isBlockLike && element.name != 'pre' && element.name != 'textarea' ) { |
149 | 149 | ||
@@ -275,10 +275,10 @@ CKEDITOR.htmlParser.fragment = function() { | |||
275 | // If the element cannot be child of the current element. | 275 | // If the element cannot be child of the current element. |
276 | if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) { | 276 | if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) { |
277 | // Current node doesn't have a close tag, time for a close | 277 | // Current node doesn't have a close tag, time for a close |
278 | // as this element isn't fit in. (#7497) | 278 | // as this element isn't fit in. (http://dev.ckeditor.com/ticket/7497) |
279 | if ( currentNode.isOptionalClose ) | 279 | if ( currentNode.isOptionalClose ) |
280 | parser.onTagClose( currentName ); | 280 | parser.onTagClose( currentName ); |
281 | // Fixing malformed nested lists by moving it into a previous list item. (#3828) | 281 | // Fixing malformed nested lists by moving it into a previous list item. (http://dev.ckeditor.com/ticket/3828) |
282 | else if ( tagName in listBlocks && currentName in listBlocks ) { | 282 | else if ( tagName in listBlocks && currentName in listBlocks ) { |
283 | var children = currentNode.children, | 283 | var children = currentNode.children, |
284 | lastChild = children[ children.length - 1 ]; | 284 | lastChild = children[ children.length - 1 ]; |
@@ -291,7 +291,7 @@ CKEDITOR.htmlParser.fragment = function() { | |||
291 | currentNode = lastChild; | 291 | currentNode = lastChild; |
292 | } | 292 | } |
293 | // Establish new list root for orphan list items, but NOT to create | 293 | // Establish new list root for orphan list items, but NOT to create |
294 | // new list for the following ones, fix them instead. (#6975) | 294 | // new list for the following ones, fix them instead. (http://dev.ckeditor.com/ticket/6975) |
295 | // <dl><dt>foo<dd>bar</dl> | 295 | // <dl><dt>foo<dd>bar</dl> |
296 | // <ul><li>foo<li>bar</ul> | 296 | // <ul><li>foo<li>bar</ul> |
297 | else if ( tagName in CKEDITOR.dtd.$listItem && | 297 | else if ( tagName in CKEDITOR.dtd.$listItem && |
@@ -409,7 +409,7 @@ CKEDITOR.htmlParser.fragment = function() { | |||
409 | var currentName = currentNode.name, | 409 | var currentName = currentNode.name, |
410 | currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd; | 410 | currentDtd = currentName ? ( CKEDITOR.dtd[ currentName ] || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ) : rootDtd; |
411 | 411 | ||
412 | // Fix orphan text in list/table. (#8540) (#8870) | 412 | // Fix orphan text in list/table. (http://dev.ckeditor.com/ticket/8540) (http://dev.ckeditor.com/ticket/8870) |
413 | if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) { | 413 | if ( !inTextarea && !currentDtd[ '#' ] && currentName in nonBreakingBlocks ) { |
414 | parser.onTagOpen( structureFixes[ currentName ] || '' ); | 414 | parser.onTagOpen( structureFixes[ currentName ] || '' ); |
415 | parser.onText( text ); | 415 | parser.onText( text ); |
diff --git a/sources/core/lang.js b/sources/core/lang.js index 8766055..06f3a2a 100644 --- a/sources/core/lang.js +++ b/sources/core/lang.js | |||
@@ -18,7 +18,7 @@ | |||
18 | */ | 18 | */ |
19 | languages: { | 19 | languages: { |
20 | af: 1, ar: 1, az: 1, bg: 1, bn: 1, bs: 1, ca: 1, cs: 1, cy: 1, da: 1, de: 1, 'de-ch': 1, el: 1, | 20 | af: 1, ar: 1, az: 1, bg: 1, bn: 1, bs: 1, ca: 1, cs: 1, cy: 1, da: 1, de: 1, 'de-ch': 1, el: 1, |
21 | 'en-au': 1, 'en-ca': 1, 'en-gb': 1, en: 1, eo: 1, es: 1, et: 1, eu: 1, fa: 1, fi: 1, fo: 1, | 21 | 'en-au': 1, 'en-ca': 1, 'en-gb': 1, en: 1, eo: 1, es: 1, 'es-mx':1, et: 1, eu: 1, fa: 1, fi: 1, fo: 1, |
22 | 'fr-ca': 1, fr: 1, gl: 1, gu: 1, he: 1, hi: 1, hr: 1, hu: 1, id: 1, is: 1, it: 1, ja: 1, ka: 1, | 22 | 'fr-ca': 1, fr: 1, gl: 1, gu: 1, he: 1, hi: 1, hr: 1, hu: 1, id: 1, is: 1, it: 1, ja: 1, ka: 1, |
23 | km: 1, ko: 1, ku: 1, lt: 1, lv: 1, mk: 1, mn: 1, ms: 1, nb: 1, nl: 1, no: 1, oc: 1, pl: 1, 'pt-br': 1, | 23 | km: 1, ko: 1, ku: 1, lt: 1, lv: 1, mk: 1, mn: 1, ms: 1, nb: 1, nl: 1, no: 1, oc: 1, pl: 1, 'pt-br': 1, |
24 | pt: 1, ro: 1, ru: 1, si: 1, sk: 1, sl: 1, sq: 1, 'sr-latn': 1, sr: 1, sv: 1, th: 1, tr: 1, tt: 1, ug: 1, | 24 | pt: 1, ro: 1, ru: 1, si: 1, sk: 1, sl: 1, sq: 1, 'sr-latn': 1, sr: 1, sv: 1, th: 1, tr: 1, tt: 1, ug: 1, |
diff --git a/sources/core/loader.js b/sources/core/loader.js index 249a8cf..dc02511 100644 --- a/sources/core/loader.js +++ b/sources/core/loader.js | |||
@@ -142,7 +142,7 @@ if ( !CKEDITOR.loader ) { | |||
142 | } | 142 | } |
143 | 143 | ||
144 | // We must guarantee the execution order of the scripts, so we | 144 | // We must guarantee the execution order of the scripts, so we |
145 | // need to load them one by one. (#4145) | 145 | // need to load them one by one. (http://dev.ckeditor.com/ticket/4145) |
146 | // The following if/else block has been taken from the scriptloader core code. | 146 | // The following if/else block has been taken from the scriptloader core code. |
147 | if ( typeof script.onreadystatechange !== 'undefined' ) { | 147 | if ( typeof script.onreadystatechange !== 'undefined' ) { |
148 | /** @ignore */ | 148 | /** @ignore */ |
@@ -156,7 +156,7 @@ if ( !CKEDITOR.loader ) { | |||
156 | /** @ignore */ | 156 | /** @ignore */ |
157 | script.onload = function() { | 157 | script.onload = function() { |
158 | // Some browsers, such as Safari, may call the onLoad function | 158 | // Some browsers, such as Safari, may call the onLoad function |
159 | // immediately. Which will break the loading sequence. (#3661) | 159 | // immediately. Which will break the loading sequence. (http://dev.ckeditor.com/ticket/3661) |
160 | setTimeout( function() { | 160 | setTimeout( function() { |
161 | onScriptLoaded( scriptName ); | 161 | onScriptLoaded( scriptName ); |
162 | }, 0 ); | 162 | }, 0 ); |
diff --git a/sources/core/scriptloader.js b/sources/core/scriptloader.js index a2b9cca..356996f 100644 --- a/sources/core/scriptloader.js +++ b/sources/core/scriptloader.js | |||
@@ -119,7 +119,7 @@ CKEDITOR.scriptLoader = ( function() { | |||
119 | } ); | 119 | } ); |
120 | 120 | ||
121 | if ( callback ) { | 121 | if ( callback ) { |
122 | // The onload or onerror event does not fire in IE8 and IE9 Quirks Mode (#14849). | 122 | // The onload or onerror event does not fire in IE8 and IE9 Quirks Mode (http://dev.ckeditor.com/ticket/14849). |
123 | if ( CKEDITOR.env.ie && ( CKEDITOR.env.version <= 8 || CKEDITOR.env.ie9Compat ) ) { | 123 | if ( CKEDITOR.env.ie && ( CKEDITOR.env.version <= 8 || CKEDITOR.env.ie9Compat ) ) { |
124 | script.$.onreadystatechange = function() { | 124 | script.$.onreadystatechange = function() { |
125 | if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) { | 125 | if ( script.$.readyState == 'loaded' || script.$.readyState == 'complete' ) { |
@@ -130,7 +130,7 @@ CKEDITOR.scriptLoader = ( function() { | |||
130 | } else { | 130 | } else { |
131 | script.$.onload = function() { | 131 | script.$.onload = function() { |
132 | // Some browsers, such as Safari, may call the onLoad function | 132 | // Some browsers, such as Safari, may call the onLoad function |
133 | // immediately. Which will break the loading sequence. (#3661) | 133 | // immediately. Which will break the loading sequence. (http://dev.ckeditor.com/ticket/3661) |
134 | setTimeout( function() { | 134 | setTimeout( function() { |
135 | onLoad( url, true ); | 135 | onLoad( url, true ); |
136 | }, 0 ); | 136 | }, 0 ); |
diff --git a/sources/core/selection.js b/sources/core/selection.js index eef28a3..d44db3b 100644 --- a/sources/core/selection.js +++ b/sources/core/selection.js | |||
@@ -6,9 +6,211 @@ | |||
6 | ( function() { | 6 | ( function() { |
7 | var isMSSelection = typeof window.getSelection != 'function', | 7 | var isMSSelection = typeof window.getSelection != 'function', |
8 | nextRev = 1, | 8 | nextRev = 1, |
9 | // #13816 | 9 | // http://dev.ckeditor.com/ticket/13816 |
10 | fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ), | 10 | fillingCharSequence = CKEDITOR.tools.repeat( '\u200b', 7 ), |
11 | fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ); | 11 | fillingCharSequenceRegExp = new RegExp( fillingCharSequence + '( )?', 'g' ), |
12 | isSelectingTable; | ||
13 | |||
14 | // #### table selection : START | ||
15 | // @param {CKEDITOR.dom.range[]} ranges | ||
16 | // @param {Boolean} allowPartially Whether a collapsed selection within table is recognized to be a valid selection. | ||
17 | // This happens for WebKits on MacOS, when you right click inside the table. | ||
18 | function isTableSelection( ranges, allowPartially ) { | ||
19 | if ( ranges.length === 0 ) { | ||
20 | return false; | ||
21 | } | ||
22 | |||
23 | var node, | ||
24 | i; | ||
25 | |||
26 | function isPartiallySelected( range ) { | ||
27 | var startCell = range.startContainer.getAscendant( { td: 1, th: 1 }, true ), | ||
28 | endCell = range.endContainer.getAscendant( { td: 1, th: 1 }, true ), | ||
29 | trim = CKEDITOR.tools.trim, | ||
30 | selected; | ||
31 | |||
32 | // Check if the selection is inside one cell and we don't have any nested table contents selected. | ||
33 | if ( !startCell || !startCell.equals( endCell ) || startCell.findOne( 'td, th, tr, tbody, table' ) ) { | ||
34 | return false; | ||
35 | } | ||
36 | |||
37 | selected = range.cloneContents(); | ||
38 | |||
39 | // Empty selection is still partially selected. | ||
40 | if ( !selected.getFirst() ) { | ||
41 | return true; | ||
42 | } | ||
43 | |||
44 | return trim( selected.getFirst().getText() ) !== trim( startCell.getText() ); | ||
45 | } | ||
46 | |||
47 | // Edge case: partially selected text node inside one table cell or cursor inside cell. | ||
48 | if ( !allowPartially && ranges.length === 1 && | ||
49 | ( ranges[ 0 ].collapsed || isPartiallySelected( ranges[ 0 ] ) ) ) { | ||
50 | return false; | ||
51 | } | ||
52 | |||
53 | for ( i = 0; i < ranges.length; i++ ) { | ||
54 | node = ranges[ i ]._getTableElement(); | ||
55 | |||
56 | if ( !node ) { | ||
57 | return false; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return true; | ||
62 | } | ||
63 | |||
64 | // After performing fake table selection, the real selection is limited | ||
65 | // to the first selected cell. Therefore to check if the real selection | ||
66 | // matches the fake selection, we check if the table cell from fake selection's | ||
67 | // first range and real selection's range are the same. | ||
68 | // Also if the selection is collapsed, we should check if it's placed inside the table | ||
69 | // in which the fake selection is or inside nested table. Such selection occurs after right mouse click. | ||
70 | function isRealTableSelection( selection, fakeSelection ) { | ||
71 | var ranges = selection.getRanges(), | ||
72 | fakeRanges = fakeSelection.getRanges(), | ||
73 | table = ranges.length && ranges[ 0 ]._getTableElement() && | ||
74 | ranges[ 0 ]._getTableElement().getAscendant( 'table', true ), | ||
75 | fakeTable = fakeRanges.length && fakeRanges[ 0 ]._getTableElement() && | ||
76 | fakeRanges[ 0 ]._getTableElement().getAscendant( 'table', true ), | ||
77 | isTableRange = ranges.length === 1 && ranges[ 0 ]._getTableElement() && | ||
78 | ranges[ 0 ]._getTableElement().is( 'table' ), | ||
79 | isFakeTableRange = fakeRanges.length === 1 && fakeRanges[ 0 ]._getTableElement() && | ||
80 | fakeRanges[ 0 ]._getTableElement().is( 'table' ); | ||
81 | |||
82 | function isValidTableSelection( table, fakeTable, ranges, fakeRanges ) { | ||
83 | var isMenuOpen = ranges.length === 1 && ranges[ 0 ].collapsed, | ||
84 | // In case of WebKit on MacOS, when checking real selection, we must allow selection to be partial. | ||
85 | // Otherwise the check will fail for table selection with opened context menu. | ||
86 | isInTable = isTableSelection( ranges, !!CKEDITOR.env.webkit ) && isTableSelection( fakeRanges ); | ||
87 | |||
88 | return isSameTable( table, fakeTable ) && ( isMenuOpen || isInTable ); | ||
89 | } | ||
90 | |||
91 | function isSameTable( table, fakeTable ) { | ||
92 | if ( !table || !fakeTable ) { | ||
93 | return false; | ||
94 | } | ||
95 | |||
96 | return table.equals( fakeTable ) || fakeTable.contains( table ); | ||
97 | } | ||
98 | |||
99 | if ( isValidTableSelection( table, fakeTable, ranges, fakeRanges ) ) { | ||
100 | // Edge case: when editor contains only table and that table is selected using selectAll command, | ||
101 | // then the selection is not properly refreshed and it must be done manually. | ||
102 | if ( isTableRange && !isFakeTableRange ) { | ||
103 | fakeSelection.selectRanges( ranges ); | ||
104 | } | ||
105 | return true; | ||
106 | } | ||
107 | |||
108 | return false; | ||
109 | } | ||
110 | |||
111 | function getSelectedCells( ranges ) { | ||
112 | var cells = [], | ||
113 | node, | ||
114 | i; | ||
115 | |||
116 | function getCellsFromElement( element ) { | ||
117 | var cells = element.find( 'td, th' ), | ||
118 | cellsArray = [], | ||
119 | i; | ||
120 | |||
121 | for ( i = 0; i < cells.count(); i++ ) { | ||
122 | cellsArray.push( cells.getItem( i ) ); | ||
123 | } | ||
124 | |||
125 | return cellsArray; | ||
126 | } | ||
127 | |||
128 | for ( i = 0; i < ranges.length; i++ ) { | ||
129 | node = ranges[ i ]._getTableElement(); | ||
130 | |||
131 | if ( node.is && node.is( { td: 1, th: 1 } ) ) { | ||
132 | cells.push( node ); | ||
133 | } else { | ||
134 | cells = cells.concat( getCellsFromElement( node ) ); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | return cells; | ||
139 | } | ||
140 | |||
141 | // Cells in the same row are separated by tab and the rows are separated by new line, e.g. | ||
142 | // Cell 1.1 Cell 1.2 | ||
143 | // Cell 2.1 Cell 2.2 | ||
144 | function getTextFromSelectedCells( ranges ) { | ||
145 | var cells = getSelectedCells( ranges ), | ||
146 | txt = '', | ||
147 | currentRow = [], | ||
148 | lastRow, | ||
149 | i; | ||
150 | |||
151 | for ( i = 0; i < cells.length; i++ ) { | ||
152 | if ( lastRow && !lastRow.equals( cells[ i ].getAscendant( 'tr' ) ) ) { | ||
153 | txt += currentRow.join( '\t' ) + '\n'; | ||
154 | lastRow = cells[ i ].getAscendant( 'tr' ); | ||
155 | currentRow = []; | ||
156 | } else if ( i === 0 ) { | ||
157 | lastRow = cells[ i ].getAscendant( 'tr' ); | ||
158 | } | ||
159 | |||
160 | currentRow.push( cells[ i ].getText() ); | ||
161 | } | ||
162 | |||
163 | txt += currentRow.join( '\t' ); | ||
164 | |||
165 | return txt; | ||
166 | } | ||
167 | |||
168 | function performFakeTableSelection( ranges ) { | ||
169 | var editor = this.root.editor, | ||
170 | realSelection = editor.getSelection( 1 ), | ||
171 | cache; | ||
172 | |||
173 | // Cleanup after previous selection - e.g. remove hidden sel container. | ||
174 | this.reset(); | ||
175 | |||
176 | // Indicate that the table is being fake-selected to prevent infinite loop | ||
177 | // inside `selectRanges`. | ||
178 | isSelectingTable = true; | ||
179 | |||
180 | // Cancel selectionchange for the real selection. | ||
181 | realSelection.root.once( 'selectionchange', function( evt ) { | ||
182 | evt.cancel(); | ||
183 | }, null, null, 0 ); | ||
184 | |||
185 | // Move real selection to the first selected range. | ||
186 | realSelection.selectRanges( [ ranges[ 0 ] ] ); | ||
187 | |||
188 | cache = this._.cache; | ||
189 | |||
190 | // Caches given ranges. | ||
191 | cache.ranges = new CKEDITOR.dom.rangeList( ranges ); | ||
192 | cache.type = CKEDITOR.SELECTION_TEXT; | ||
193 | cache.selectedElement = ranges[ 0 ]._getTableElement(); | ||
194 | |||
195 | // `selectedText` should contain text from all selected data ("plain text table") | ||
196 | // to be compatible with Firefox's implementation. | ||
197 | cache.selectedText = getTextFromSelectedCells( ranges ); | ||
198 | |||
199 | // Properties that will not be available when isFake. | ||
200 | cache.nativeSel = null; | ||
201 | |||
202 | this.isFake = 1; | ||
203 | this.rev = nextRev++; | ||
204 | |||
205 | // Save this selection, so it can be returned by editor.getSelection(). | ||
206 | editor._.fakeSelection = this; | ||
207 | |||
208 | isSelectingTable = false; | ||
209 | |||
210 | // Fire selectionchange, just like a normal selection. | ||
211 | this.root.fire( 'selectionchange' ); | ||
212 | } | ||
213 | // #### table selection : END | ||
12 | 214 | ||
13 | // #### checkSelectionChange : START | 215 | // #### checkSelectionChange : START |
14 | 216 | ||
@@ -22,10 +224,9 @@ | |||
22 | 224 | ||
23 | if ( sel ) { | 225 | if ( sel ) { |
24 | realSel = this.getSelection( 1 ); | 226 | realSel = this.getSelection( 1 ); |
25 | 227 | // If real (not locked/stored) selection was moved from hidden container | |
26 | // If real (not locked/stored) selection was moved from hidden container, | 228 | // or is not a table one, then the fake-selection must be invalidated. |
27 | // then the fake-selection must be invalidated. | 229 | if ( !realSel || ( !realSel.isHidden() && !isRealTableSelection( realSel, sel ) ) ) { |
28 | if ( !realSel || !realSel.isHidden() ) { | ||
29 | // Remove the cache from fake-selection references in use elsewhere. | 230 | // Remove the cache from fake-selection references in use elsewhere. |
30 | sel.reset(); | 231 | sel.reset(); |
31 | 232 | ||
@@ -47,7 +248,7 @@ | |||
47 | 248 | ||
48 | var currentPath = this.elementPath(); | 249 | var currentPath = this.elementPath(); |
49 | if ( !currentPath.compare( this._.selectionPreviousPath ) ) { | 250 | if ( !currentPath.compare( this._.selectionPreviousPath ) ) { |
50 | // Handle case when dialog inserts new element but parent block and path (so also focus context) does not change. (#13362) | 251 | // Handle case when dialog inserts new element but parent block and path (so also focus context) does not change. (http://dev.ckeditor.com/ticket/13362) |
51 | var sameBlockParent = this._.selectionPreviousPath && this._.selectionPreviousPath.blockLimit.equals( currentPath.blockLimit ); | 252 | var sameBlockParent = this._.selectionPreviousPath && this._.selectionPreviousPath.blockLimit.equals( currentPath.blockLimit ); |
52 | // Cache the active element, which we'll eventually lose on Webkit. | 253 | // Cache the active element, which we'll eventually lose on Webkit. |
53 | if ( CKEDITOR.env.webkit && !sameBlockParent ) | 254 | if ( CKEDITOR.env.webkit && !sameBlockParent ) |
@@ -97,7 +298,7 @@ | |||
97 | // * is a visible node, | 298 | // * is a visible node, |
98 | // * is a non-empty element (this rule will accept elements like <strong></strong> because they | 299 | // * is a non-empty element (this rule will accept elements like <strong></strong> because they |
99 | // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret). | 300 | // they were not accepted by the isVisible() check, not not <br> which cannot absorb the caret). |
100 | // See #12621. | 301 | // See http://dev.ckeditor.com/ticket/12621. |
101 | function mayAbsorbCaret( node ) { | 302 | function mayAbsorbCaret( node ) { |
102 | if ( isVisible( node ) ) | 303 | if ( isVisible( node ) ) |
103 | return true; | 304 | return true; |
@@ -138,8 +339,8 @@ | |||
138 | if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) ) | 339 | if ( ctxRequiresFix( previous ) || ctxRequiresFix( next, 1 ) ) |
139 | return true; | 340 | return true; |
140 | 341 | ||
141 | // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (#7222) | 342 | // Empty block/inline element is also affected. <span>^</span>, <p>^</p> (http://dev.ckeditor.com/ticket/7222) |
142 | // If you found this line confusing check #12655. | 343 | // If you found this line confusing check http://dev.ckeditor.com/ticket/12655. |
143 | if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) ) | 344 | if ( !( previous || next ) && !( ct.type == CKEDITOR.NODE_ELEMENT && ct.isBlockBoundary() && ct.getBogus() ) ) |
144 | return true; | 345 | return true; |
145 | 346 | ||
@@ -155,7 +356,7 @@ | |||
155 | return fillingChar; | 356 | return fillingChar; |
156 | } | 357 | } |
157 | 358 | ||
158 | // Checks if a filling char has been used, eventualy removing it (#1272). | 359 | // Checks if a filling char has been used, eventually removing it (http://dev.ckeditor.com/ticket/1272). |
159 | function checkFillingCharSequenceNodeReady( editable ) { | 360 | function checkFillingCharSequenceNodeReady( editable ) { |
160 | var fillingChar = editable.getCustomData( 'cke-fillingChar' ); | 361 | var fillingChar = editable.getCustomData( 'cke-fillingChar' ); |
161 | 362 | ||
@@ -164,6 +365,7 @@ | |||
164 | // creating it. | 365 | // creating it. |
165 | if ( fillingChar.getCustomData( 'ready' ) ) { | 366 | if ( fillingChar.getCustomData( 'ready' ) ) { |
166 | removeFillingCharSequenceNode( editable ); | 367 | removeFillingCharSequenceNode( editable ); |
368 | editable.editor.fire( 'selectionCheck' ); | ||
167 | } else { | 369 | } else { |
168 | fillingChar.setCustomData( 'ready', 1 ); | 370 | fillingChar.setCustomData( 'ready', 1 ); |
169 | } | 371 | } |
@@ -175,7 +377,7 @@ | |||
175 | 377 | ||
176 | if ( fillingChar ) { | 378 | if ( fillingChar ) { |
177 | // Text selection position might get mangled by | 379 | // Text selection position might get mangled by |
178 | // subsequent dom modification, save it now for restoring. (#8617) | 380 | // subsequent dom modification, save it now for restoring. (http://dev.ckeditor.com/ticket/8617) |
179 | if ( keepSelection !== false ) { | 381 | if ( keepSelection !== false ) { |
180 | var sel = editable.getDocument().getSelection().getNative(), | 382 | var sel = editable.getDocument().getSelection().getNative(), |
181 | // Be error proof. | 383 | // Be error proof. |
@@ -211,11 +413,11 @@ | |||
211 | } | 413 | } |
212 | } | 414 | } |
213 | 415 | ||
214 | // #13816 | 416 | // http://dev.ckeditor.com/ticket/13816 |
215 | function removeFillingCharSequenceString( str, nbspAware ) { | 417 | function removeFillingCharSequenceString( str, nbspAware ) { |
216 | if ( nbspAware ) { | 418 | if ( nbspAware ) { |
217 | return str.replace( fillingCharSequenceRegExp, function( m, p ) { | 419 | return str.replace( fillingCharSequenceRegExp, function( m, p ) { |
218 | // #10291 if filling char is followed by a space replace it with NBSP. | 420 | // http://dev.ckeditor.com/ticket/10291 if filling char is followed by a space replace it with NBSP. |
219 | return p ? '\xa0' : ''; | 421 | return p ? '\xa0' : ''; |
220 | } ); | 422 | } ); |
221 | } else { | 423 | } else { |
@@ -391,7 +593,7 @@ | |||
391 | ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) { | 593 | ( enclosedNode = range.getEnclosedNode() ) && enclosedNode.type == CKEDITOR.NODE_ELEMENT ) { |
392 | // So far we can't say that enclosed element is non-editable. Before checking, | 594 | // So far we can't say that enclosed element is non-editable. Before checking, |
393 | // we'll shrink range (clone). Shrinking will stop on non-editable range, or | 595 | // we'll shrink range (clone). Shrinking will stop on non-editable range, or |
394 | // innermost element (#11114). | 596 | // innermost element (http://dev.ckeditor.com/ticket/11114). |
395 | clone = range.clone(); | 597 | clone = range.clone(); |
396 | clone.shrink( CKEDITOR.SHRINK_ELEMENT, true ); | 598 | clone.shrink( CKEDITOR.SHRINK_ELEMENT, true ); |
397 | 599 | ||
@@ -525,7 +727,7 @@ | |||
525 | 727 | ||
526 | // Give the editable an initial selection on first focus, | 728 | // Give the editable an initial selection on first focus, |
527 | // put selection at a consistent position at the start | 729 | // put selection at a consistent position at the start |
528 | // of the contents. (#9507) | 730 | // of the contents. (http://dev.ckeditor.com/ticket/9507) |
529 | if ( CKEDITOR.env.gecko ) { | 731 | if ( CKEDITOR.env.gecko ) { |
530 | editable.attachListener( editable, 'focus', function( evt ) { | 732 | editable.attachListener( editable, 'focus', function( evt ) { |
531 | evt.removeListener(); | 733 | evt.removeListener(); |
@@ -533,7 +735,7 @@ | |||
533 | if ( restoreSel !== 0 ) { | 735 | if ( restoreSel !== 0 ) { |
534 | var nativ = editor.getSelection().getNative(); | 736 | var nativ = editor.getSelection().getNative(); |
535 | // Do it only if the native selection is at an unwanted | 737 | // Do it only if the native selection is at an unwanted |
536 | // place (at the very start of the editable). #10119 | 738 | // place (at the very start of the editable). http://dev.ckeditor.com/ticket/10119 |
537 | if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) { | 739 | if ( nativ && nativ.isCollapsed && nativ.anchorNode == editable.$ ) { |
538 | var rng = editor.createRange(); | 740 | var rng = editor.createRange(); |
539 | rng.moveToElementEditStart( editable ); | 741 | rng.moveToElementEditStart( editable ); |
@@ -553,7 +755,7 @@ | |||
553 | 755 | ||
554 | // On Webkit when editor uses divarea, native focus causes editable viewport to scroll | 756 | // On Webkit when editor uses divarea, native focus causes editable viewport to scroll |
555 | // to the top (when there is no active selection inside while focusing) so the scroll | 757 | // to the top (when there is no active selection inside while focusing) so the scroll |
556 | // position should be restored after focusing back editable area. (#14659) | 758 | // position should be restored after focusing back editable area. (http://dev.ckeditor.com/ticket/14659) |
557 | if ( restoreSel && editor._.previousScrollTop != null && editor._.previousScrollTop != editable.$.scrollTop ) { | 759 | if ( restoreSel && editor._.previousScrollTop != null && editor._.previousScrollTop != editable.$.scrollTop ) { |
558 | editable.$.scrollTop = editor._.previousScrollTop; | 760 | editable.$.scrollTop = editor._.previousScrollTop; |
559 | } | 761 | } |
@@ -605,7 +807,7 @@ | |||
605 | editable.attachListener( editable, 'mousedown', function( evt ) { | 807 | editable.attachListener( editable, 'mousedown', function( evt ) { |
606 | // IE scrolls document to top on right mousedown | 808 | // IE scrolls document to top on right mousedown |
607 | // when editor has no focus, remember this scroll | 809 | // when editor has no focus, remember this scroll |
608 | // position and revert it before context menu opens. (#5778) | 810 | // position and revert it before context menu opens. (http://dev.ckeditor.com/ticket/5778) |
609 | if ( evt.data.$.button == 2 ) { | 811 | if ( evt.data.$.button == 2 ) { |
610 | var sel = editor.document.getSelection(); | 812 | var sel = editor.document.getSelection(); |
611 | if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE ) | 813 | if ( !sel || sel.getType() == CKEDITOR.SELECTION_NONE ) |
@@ -624,7 +826,7 @@ | |||
624 | 826 | ||
625 | // When content doc is in standards mode, IE doesn't focus the editor when | 827 | // When content doc is in standards mode, IE doesn't focus the editor when |
626 | // clicking at the region below body (on html element) content, we emulate | 828 | // clicking at the region below body (on html element) content, we emulate |
627 | // the normal behavior on old IEs. (#1659, #7932) | 829 | // the normal behavior on old IEs. (http://dev.ckeditor.com/ticket/1659, http://dev.ckeditor.com/ticket/7932) |
628 | if ( doc.$.compatMode != 'BackCompat' ) { | 830 | if ( doc.$.compatMode != 'BackCompat' ) { |
629 | if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { | 831 | if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { |
630 | var textRng, | 832 | var textRng, |
@@ -661,7 +863,7 @@ | |||
661 | html.removeListener( 'mousemove', onHover ); | 863 | html.removeListener( 'mousemove', onHover ); |
662 | removeListeners(); | 864 | removeListeners(); |
663 | 865 | ||
664 | // Make it in effect on mouse up. (#9022) | 866 | // Make it in effect on mouse up. (http://dev.ckeditor.com/ticket/9022) |
665 | textRng.select(); | 867 | textRng.select(); |
666 | } | 868 | } |
667 | 869 | ||
@@ -690,7 +892,7 @@ | |||
690 | if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) { | 892 | if ( CKEDITOR.env.version > 7 && CKEDITOR.env.version < 11 ) { |
691 | html.on( 'mousedown', function( evt ) { | 893 | html.on( 'mousedown', function( evt ) { |
692 | if ( evt.data.getTarget().is( 'html' ) ) { | 894 | if ( evt.data.getTarget().is( 'html' ) ) { |
693 | // Limit the text selection mouse move inside of editable. (#9715) | 895 | // Limit the text selection mouse move inside of editable. (http://dev.ckeditor.com/ticket/9715) |
694 | outerDoc.on( 'mouseup', onSelectEnd ); | 896 | outerDoc.on( 'mouseup', onSelectEnd ); |
695 | html.on( 'mouseup', onSelectEnd ); | 897 | html.on( 'mouseup', onSelectEnd ); |
696 | } | 898 | } |
@@ -704,6 +906,14 @@ | |||
704 | // 2. After the accomplish of keyboard and mouse events. | 906 | // 2. After the accomplish of keyboard and mouse events. |
705 | editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor ); | 907 | editable.attachListener( editable, 'selectionchange', checkSelectionChange, editor ); |
706 | editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor ); | 908 | editable.attachListener( editable, 'keyup', checkSelectionChangeTimeout, editor ); |
909 | // http://dev.ckeditor.com/ticket/14407 - Don't even let anything happen if the selection is in a non-editable element. | ||
910 | editable.attachListener( editable, 'keydown', function( evt ) { | ||
911 | var sel = this.getSelection( 1 ); | ||
912 | if ( nonEditableAscendant( sel ) ) { | ||
913 | sel.selectElement( nonEditableAscendant( sel ) ); | ||
914 | evt.data.preventDefault(); | ||
915 | } | ||
916 | }, editor ); | ||
707 | // Always fire the selection change on focus gain. | 917 | // Always fire the selection change on focus gain. |
708 | // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and | 918 | // On Webkit do this on DOMFocusIn, because the selection is unlocked on it too and |
709 | // we need synchronization between those listeners to not lost cached editor._.previousActive property | 919 | // we need synchronization between those listeners to not lost cached editor._.previousActive property |
@@ -713,7 +923,7 @@ | |||
713 | editor.selectionChange( 1 ); | 923 | editor.selectionChange( 1 ); |
714 | } ); | 924 | } ); |
715 | 925 | ||
716 | // #9699: On Webkit&Gecko in inline editor we have to check selection when it was changed | 926 | // http://dev.ckeditor.com/ticket/9699: On Webkit&Gecko in inline editor we have to check selection when it was changed |
717 | // by dragging and releasing mouse button outside editable. Dragging (mousedown) | 927 | // by dragging and releasing mouse button outside editable. Dragging (mousedown) |
718 | // has to be initialized in editable, but for mouseup we listen on document element. | 928 | // has to be initialized in editable, but for mouseup we listen on document element. |
719 | if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) { | 929 | if ( isInline && ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) ) { |
@@ -727,11 +937,11 @@ | |||
727 | mouseDown = 0; | 937 | mouseDown = 0; |
728 | } ); | 938 | } ); |
729 | } | 939 | } |
730 | // In all other cases listen on simple mouseup over editable, as we did before #9699. | 940 | // In all other cases listen on simple mouseup over editable, as we did before http://dev.ckeditor.com/ticket/9699. |
731 | // | 941 | // |
732 | // Use document instead of editable in non-IEs for observing mouseup | 942 | // Use document instead of editable in non-IEs for observing mouseup |
733 | // since editable won't fire the event if selection process started within iframe and ended out | 943 | // since editable won't fire the event if selection process started within iframe and ended out |
734 | // of the editor (#9851). | 944 | // of the editor (http://dev.ckeditor.com/ticket/9851). |
735 | else { | 945 | else { |
736 | editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor ); | 946 | editable.attachListener( CKEDITOR.env.ie ? editable : doc.getDocumentElement(), 'mouseup', checkSelectionChangeTimeout, editor ); |
737 | } | 947 | } |
@@ -753,18 +963,18 @@ | |||
753 | case 8: // BACKSPACE | 963 | case 8: // BACKSPACE |
754 | case 45: // INS | 964 | case 45: // INS |
755 | case 46: // DEl | 965 | case 46: // DEl |
756 | removeFillingCharSequenceNode( editable ); | 966 | if ( editable.hasFocus ) { |
967 | removeFillingCharSequenceNode( editable ); | ||
968 | } | ||
757 | } | 969 | } |
758 | 970 | ||
759 | }, null, null, -1 ); | 971 | }, null, null, -1 ); |
760 | } | 972 | } |
761 | 973 | ||
762 | // Automatically select non-editable element when navigating into | ||
763 | // it by left/right or backspace/del keys. | ||
764 | editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 ); | 974 | editable.attachListener( editable, 'keydown', getOnKeyDownListener( editor ), null, null, -1 ); |
765 | 975 | ||
766 | function moveRangeToPoint( range, x, y ) { | 976 | function moveRangeToPoint( range, x, y ) { |
767 | // Error prune in IE7. (#9034, #9110) | 977 | // Error prune in IE7. (http://dev.ckeditor.com/ticket/9034, http://dev.ckeditor.com/ticket/9110) |
768 | try { | 978 | try { |
769 | range.moveToPoint( x, y ); | 979 | range.moveToPoint( x, y ); |
770 | } catch ( e ) {} | 980 | } catch ( e ) {} |
@@ -785,14 +995,27 @@ | |||
785 | range = sel.createRange(); | 995 | range = sel.createRange(); |
786 | 996 | ||
787 | // The selection range is reported on host, but actually it should applies to the content doc. | 997 | // The selection range is reported on host, but actually it should applies to the content doc. |
788 | if ( sel.type != 'None' && range.parentElement().ownerDocument == doc.$ ) | 998 | // The parentElement may be null for read only mode in IE10 and below (http://dev.ckeditor.com/ticket/9780). |
999 | if ( sel.type != 'None' && range.parentElement() && range.parentElement().ownerDocument == doc.$ ) | ||
789 | range.select(); | 1000 | range.select(); |
790 | } | 1001 | } |
1002 | |||
1003 | function nonEditableAscendant( sel ) { | ||
1004 | if ( CKEDITOR.env.ie ) { | ||
1005 | var range = sel.getRanges()[ 0 ], | ||
1006 | ascendant = range ? range.startContainer.getAscendant( function( parent ) { | ||
1007 | return parent.type == CKEDITOR.NODE_ELEMENT && | ||
1008 | ( parent.getAttribute( 'contenteditable' ) == 'false' || parent.getAttribute( 'contenteditable' ) == 'true' ); | ||
1009 | }, true ) : null ; | ||
1010 | |||
1011 | return range && ascendant.getAttribute( 'contenteditable' ) == 'false' && ascendant; | ||
1012 | } | ||
1013 | } | ||
791 | } ); | 1014 | } ); |
792 | 1015 | ||
793 | editor.on( 'setData', function() { | 1016 | editor.on( 'setData', function() { |
794 | // Invalidate locked selection when unloading DOM. | 1017 | // Invalidate locked selection when unloading DOM. |
795 | // (#9521, #5217#comment:32 and #11500#comment:11) | 1018 | // (http://dev.ckeditor.com/ticket/9521, http://dev.ckeditor.com/ticket/5217#comment:32 and http://dev.ckeditor.com/ticket/11500#comment:11) |
796 | editor.unlockSelection(); | 1019 | editor.unlockSelection(); |
797 | 1020 | ||
798 | // Webkit's selection will mess up after the data loading. | 1021 | // Webkit's selection will mess up after the data loading. |
@@ -806,7 +1029,7 @@ | |||
806 | editor.unlockSelection(); | 1029 | editor.unlockSelection(); |
807 | } ); | 1030 | } ); |
808 | 1031 | ||
809 | // IE9 might cease to work if there's an object selection inside the iframe (#7639). | 1032 | // IE9 might cease to work if there's an object selection inside the iframe (http://dev.ckeditor.com/ticket/7639). |
810 | if ( CKEDITOR.env.ie9Compat ) | 1033 | if ( CKEDITOR.env.ie9Compat ) |
811 | editor.on( 'beforeDestroy', clearSelection, null, null, 9 ); | 1034 | editor.on( 'beforeDestroy', clearSelection, null, null, 9 ); |
812 | 1035 | ||
@@ -822,7 +1045,7 @@ | |||
822 | // When loaded data are ready check whether hidden selection container was not loaded. | 1045 | // When loaded data are ready check whether hidden selection container was not loaded. |
823 | editor.on( 'loadSnapshot', function() { | 1046 | editor.on( 'loadSnapshot', function() { |
824 | var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ), | 1047 | var isElement = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ), |
825 | // TODO replace with el.find() which will be introduced in #9764, | 1048 | // TODO replace with el.find() which will be introduced in http://dev.ckeditor.com/ticket/9764, |
826 | // because it may happen that hidden sel container won't be the last element. | 1049 | // because it may happen that hidden sel container won't be the last element. |
827 | last = editor.editable().getLast( isElement ); | 1050 | last = editor.editable().getLast( isElement ); |
828 | 1051 | ||
@@ -869,7 +1092,7 @@ | |||
869 | } ); | 1092 | } ); |
870 | 1093 | ||
871 | // On WebKit only, we need a special "filling" char on some situations | 1094 | // On WebKit only, we need a special "filling" char on some situations |
872 | // (#1272). Here we set the events that should invalidate that char. | 1095 | // (http://dev.ckeditor.com/ticket/1272). Here we set the events that should invalidate that char. |
873 | if ( CKEDITOR.env.webkit ) { | 1096 | if ( CKEDITOR.env.webkit ) { |
874 | CKEDITOR.on( 'instanceReady', function( evt ) { | 1097 | CKEDITOR.on( 'instanceReady', function( evt ) { |
875 | var editor = evt.editor; | 1098 | var editor = evt.editor; |
@@ -884,7 +1107,7 @@ | |||
884 | 1107 | ||
885 | // Filter Undo snapshot's HTML to get rid of Filling Char Sequence. | 1108 | // Filter Undo snapshot's HTML to get rid of Filling Char Sequence. |
886 | // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's | 1109 | // Note: CKEDITOR.dom.range.createBookmark2() normalizes snapshot's |
887 | // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (#13816). | 1110 | // bookmarks to anticipate the removal of FCSeq from the snapshot's HTML (http://dev.ckeditor.com/ticket/13816). |
888 | editor.on( 'getSnapshot', function( evt ) { | 1111 | editor.on( 'getSnapshot', function( evt ) { |
889 | if ( evt.data ) { | 1112 | if ( evt.data ) { |
890 | evt.data = removeFillingCharSequenceString( evt.data ); | 1113 | evt.data = removeFillingCharSequenceString( evt.data ); |
@@ -893,7 +1116,7 @@ | |||
893 | 1116 | ||
894 | // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat | 1117 | // Filter data to get rid of Filling Char Sequence. Filter on #toDataFormat |
895 | // instead of #getData because once removed, FCSeq may leave an empty element, | 1118 | // instead of #getData because once removed, FCSeq may leave an empty element, |
896 | // which should be pruned by the dataProcessor (#13816). | 1119 | // which should be pruned by the dataProcessor (http://dev.ckeditor.com/ticket/13816). |
897 | // Note: Used low priority to filter when dataProcessor works on strings, | 1120 | // Note: Used low priority to filter when dataProcessor works on strings, |
898 | // not pseudo–DOM. | 1121 | // not pseudo–DOM. |
899 | editor.on( 'toDataFormat', function( evt ) { | 1122 | editor.on( 'toDataFormat', function( evt ) { |
@@ -1138,7 +1361,7 @@ | |||
1138 | 1361 | ||
1139 | // Selection out of concerned range, empty the selection. | 1362 | // Selection out of concerned range, empty the selection. |
1140 | // TODO check whether this condition cannot be reverted to its old | 1363 | // TODO check whether this condition cannot be reverted to its old |
1141 | // form (commented out) after we closed #10438. | 1364 | // form (commented out) after we closed http://dev.ckeditor.com/ticket/10438. |
1142 | //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) { | 1365 | //if ( !( rangeParent && ( root.equals( rangeParent ) || root.contains( rangeParent ) ) ) ) { |
1143 | if ( !( | 1366 | if ( !( |
1144 | rangeParent && | 1367 | rangeParent && |
@@ -1180,7 +1403,7 @@ | |||
1180 | * | 1403 | * |
1181 | * var selection = editor.getSelection().getNative(); | 1404 | * var selection = editor.getSelection().getNative(); |
1182 | * | 1405 | * |
1183 | * @returns {Object} The native browser selection object. | 1406 | * @returns {Object} The native browser selection object or null if this is a fake selection. |
1184 | */ | 1407 | */ |
1185 | getNative: function() { | 1408 | getNative: function() { |
1186 | if ( this._.cache.nativeSel !== undefined ) | 1409 | if ( this._.cache.nativeSel !== undefined ) |
@@ -1272,7 +1495,7 @@ | |||
1272 | * alert( ranges.length ); | 1495 | * alert( ranges.length ); |
1273 | * | 1496 | * |
1274 | * @method | 1497 | * @method |
1275 | * @param {Boolean} [onlyEditables] If set to `true`, this function retrives editable ranges only. | 1498 | * @param {Boolean} [onlyEditables] If set to `true`, this function retrieves editable ranges only. |
1276 | * @returns {Array} Range instances that represent the current selection. | 1499 | * @returns {Array} Range instances that represent the current selection. |
1277 | */ | 1500 | */ |
1278 | getRanges: ( function() { | 1501 | getRanges: ( function() { |
@@ -1303,7 +1526,7 @@ | |||
1303 | index = -1, | 1526 | index = -1, |
1304 | position, distance, container; | 1527 | position, distance, container; |
1305 | 1528 | ||
1306 | // Binary search over all element childs to test the range to see whether | 1529 | // Binary search over all element children to test the range to see whether |
1307 | // range is right on the boundary of one element. | 1530 | // range is right on the boundary of one element. |
1308 | while ( startIndex <= endIndex ) { | 1531 | while ( startIndex <= endIndex ) { |
1309 | index = Math.floor( ( startIndex + endIndex ) / 2 ); | 1532 | index = Math.floor( ( startIndex + endIndex ) / 2 ); |
@@ -1319,8 +1542,8 @@ | |||
1319 | return { container: parent, offset: getNodeIndex( child ) }; | 1542 | return { container: parent, offset: getNodeIndex( child ) }; |
1320 | } | 1543 | } |
1321 | 1544 | ||
1322 | // All childs are text nodes, | 1545 | // All children are text nodes, |
1323 | // or to the right hand of test range are all text nodes. (#6992) | 1546 | // or to the right hand of test range are all text nodes. (http://dev.ckeditor.com/ticket/6992) |
1324 | if ( index == -1 || index == siblings.length - 1 && position < 0 ) { | 1547 | if ( index == -1 || index == siblings.length - 1 && position < 0 ) { |
1325 | // Adapt test range to embrace the entire parent contents. | 1548 | // Adapt test range to embrace the entire parent contents. |
1326 | testRange.moveToElementText( parent ); | 1549 | testRange.moveToElementText( parent ); |
@@ -1328,7 +1551,7 @@ | |||
1328 | 1551 | ||
1329 | // IE report line break as CRLF with range.text but | 1552 | // IE report line break as CRLF with range.text but |
1330 | // only LF with textnode.nodeValue, normalize them to avoid | 1553 | // only LF with textnode.nodeValue, normalize them to avoid |
1331 | // breaking character counting logic below. (#3949) | 1554 | // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949) |
1332 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; | 1555 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; |
1333 | 1556 | ||
1334 | siblings = parent.childNodes; | 1557 | siblings = parent.childNodes; |
@@ -1364,7 +1587,7 @@ | |||
1364 | 1587 | ||
1365 | // IE report line break as CRLF with range.text but | 1588 | // IE report line break as CRLF with range.text but |
1366 | // only LF with textnode.nodeValue, normalize them to avoid | 1589 | // only LF with textnode.nodeValue, normalize them to avoid |
1367 | // breaking character counting logic below. (#3949) | 1590 | // breaking character counting logic below. (http://dev.ckeditor.com/ticket/3949) |
1368 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; | 1591 | distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; |
1369 | 1592 | ||
1370 | // Actual range anchor right beside test range at the inner boundary of text node. | 1593 | // Actual range anchor right beside test range at the inner boundary of text node. |
@@ -1381,7 +1604,7 @@ | |||
1381 | } | 1604 | } |
1382 | child = sibling; | 1605 | child = sibling; |
1383 | } | 1606 | } |
1384 | // Measurement in IE could be somtimes wrong because of <select> element. (#4611) | 1607 | // Measurement in IE could be sometimes wrong because of <select> element. (http://dev.ckeditor.com/ticket/4611) |
1385 | catch ( e ) { | 1608 | catch ( e ) { |
1386 | return { container: parent, offset: getNodeIndex( child ) }; | 1609 | return { container: parent, offset: getNodeIndex( child ) }; |
1387 | } | 1610 | } |
@@ -1413,7 +1636,7 @@ | |||
1413 | boundaryInfo = getBoundaryInformation( nativeRange ); | 1636 | boundaryInfo = getBoundaryInformation( nativeRange ); |
1414 | range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); | 1637 | range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); |
1415 | 1638 | ||
1416 | // Correct an invalid IE range case on empty list item. (#5850) | 1639 | // Correct an invalid IE range case on empty list item. (http://dev.ckeditor.com/ticket/5850) |
1417 | if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() ) | 1640 | if ( range.endContainer.getPosition( range.startContainer ) & CKEDITOR.POSITION_PRECEDING && range.endOffset <= range.startContainer.getIndex() ) |
1418 | range.collapse(); | 1641 | range.collapse(); |
1419 | 1642 | ||
@@ -1445,7 +1668,7 @@ | |||
1445 | } )() : | 1668 | } )() : |
1446 | function() { | 1669 | function() { |
1447 | // On browsers implementing the W3C range, we simply | 1670 | // On browsers implementing the W3C range, we simply |
1448 | // tranform the native ranges in CKEDITOR.dom.range | 1671 | // transform the native ranges in CKEDITOR.dom.range |
1449 | // instances. | 1672 | // instances. |
1450 | 1673 | ||
1451 | var ranges = [], | 1674 | var ranges = [], |
@@ -1478,7 +1701,7 @@ | |||
1478 | return ranges; | 1701 | return ranges; |
1479 | 1702 | ||
1480 | // Split range into multiple by read-only nodes. | 1703 | // Split range into multiple by read-only nodes. |
1481 | // Clone ranges array to avoid changing cached ranges (#11493). | 1704 | // Clone ranges array to avoid changing cached ranges (http://dev.ckeditor.com/ticket/11493). |
1482 | return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) ); | 1705 | return extractEditableRanges( new CKEDITOR.dom.rangeList( ranges.slice() ) ); |
1483 | }; | 1706 | }; |
1484 | } )(), | 1707 | } )(), |
@@ -1512,11 +1735,11 @@ | |||
1512 | 1735 | ||
1513 | // Decrease the range content to exclude particial | 1736 | // Decrease the range content to exclude particial |
1514 | // selected node on the start which doesn't have | 1737 | // selected node on the start which doesn't have |
1515 | // visual impact. ( #3231 ) | 1738 | // visual impact. ( http://dev.ckeditor.com/ticket/3231 ) |
1516 | while ( 1 ) { | 1739 | while ( 1 ) { |
1517 | var startContainer = range.startContainer, | 1740 | var startContainer = range.startContainer, |
1518 | startOffset = range.startOffset; | 1741 | startOffset = range.startOffset; |
1519 | // Limit the fix only to non-block elements.(#3950) | 1742 | // Limit the fix only to non-block elements.(http://dev.ckeditor.com/ticket/3950) |
1520 | if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() ) | 1743 | if ( startOffset == ( startContainer.getChildCount ? startContainer.getChildCount() : startContainer.getLength() ) && !startContainer.isBlockBoundary() ) |
1521 | range.setStartAfter( startContainer ); | 1744 | range.setStartAfter( startContainer ); |
1522 | else | 1745 | else |
@@ -1558,7 +1781,7 @@ | |||
1558 | * var element = editor.getSelection().getSelectedElement(); | 1781 | * var element = editor.getSelection().getSelectedElement(); |
1559 | * alert( element.getName() ); | 1782 | * alert( element.getName() ); |
1560 | * | 1783 | * |
1561 | * @returns {CKEDITOR.dom.element} The selected element. Null if no | 1784 | * @returns {CKEDITOR.dom.element/null} The selected element. `null` if no |
1562 | * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}. | 1785 | * selection is available or the selection type is not {@link CKEDITOR#SELECTION_ELEMENT}. |
1563 | */ | 1786 | */ |
1564 | getSelectedElement: function() { | 1787 | getSelectedElement: function() { |
@@ -1641,7 +1864,7 @@ | |||
1641 | 1864 | ||
1642 | if ( restore ) { | 1865 | if ( restore ) { |
1643 | var selectedElement = this.getSelectedElement(), | 1866 | var selectedElement = this.getSelectedElement(), |
1644 | ranges = !selectedElement && this.getRanges(), | 1867 | ranges = this.getRanges(), |
1645 | faked = this.isFake; | 1868 | faked = this.isFake; |
1646 | } | 1869 | } |
1647 | 1870 | ||
@@ -1655,7 +1878,10 @@ | |||
1655 | if ( !( common && common.getAscendant( 'body', 1 ) ) ) | 1878 | if ( !( common && common.getAscendant( 'body', 1 ) ) ) |
1656 | return; | 1879 | return; |
1657 | 1880 | ||
1658 | if ( faked ) | 1881 | if ( isTableSelection( ranges ) ) { |
1882 | // Tables have it's own selection method. | ||
1883 | performFakeTableSelection.call( this, ranges ); | ||
1884 | } else if ( faked ) | ||
1659 | this.fake( selectedElement ); | 1885 | this.fake( selectedElement ); |
1660 | else if ( selectedElement ) | 1886 | else if ( selectedElement ) |
1661 | this.selectElement( selectedElement ); | 1887 | this.selectElement( selectedElement ); |
@@ -1727,7 +1953,7 @@ | |||
1727 | // Check if there's a hiddenSelectionContainer in editable at some index. | 1953 | // Check if there's a hiddenSelectionContainer in editable at some index. |
1728 | // Some ranges may be anchored after the hiddenSelectionContainer and, | 1954 | // Some ranges may be anchored after the hiddenSelectionContainer and, |
1729 | // once the container is removed while resetting the selection, they | 1955 | // once the container is removed while resetting the selection, they |
1730 | // may need new endOffset (one element less within the range) (#11021 #11393). | 1956 | // may need new endOffset (one element less within the range) (http://dev.ckeditor.com/ticket/11021 http://dev.ckeditor.com/ticket/11393). |
1731 | if ( hadHiddenSelectionContainer ) | 1957 | if ( hadHiddenSelectionContainer ) |
1732 | fixRangesAfterHiddenSelectionContainer( ranges, this.root ); | 1958 | fixRangesAfterHiddenSelectionContainer( ranges, this.root ); |
1733 | 1959 | ||
@@ -1755,6 +1981,15 @@ | |||
1755 | return; | 1981 | return; |
1756 | } | 1982 | } |
1757 | 1983 | ||
1984 | // Handle special case - fake selection of table cells. | ||
1985 | if ( editor && editor.plugins.tableselection && | ||
1986 | CKEDITOR.plugins.tableselection.isSupportedEnvironment && | ||
1987 | isTableSelection( ranges ) && !isSelectingTable | ||
1988 | ) { | ||
1989 | performFakeTableSelection.call( this, ranges ); | ||
1990 | return; | ||
1991 | } | ||
1992 | |||
1758 | if ( isMSSelection ) { | 1993 | if ( isMSSelection ) { |
1759 | var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), | 1994 | var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), |
1760 | fillerTextRegex = /\ufeff|\u00a0/, | 1995 | fillerTextRegex = /\ufeff|\u00a0/, |
@@ -1788,7 +2023,7 @@ | |||
1788 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells || | 2023 | if ( range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in nonCells || |
1789 | range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) { | 2024 | range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in nonCells ) { |
1790 | range.shrink( CKEDITOR.NODE_ELEMENT, true ); | 2025 | range.shrink( CKEDITOR.NODE_ELEMENT, true ); |
1791 | // The range might get collapsed (#7975). Update cached variable. | 2026 | // The range might get collapsed (http://dev.ckeditor.com/ticket/7975). Update cached variable. |
1792 | collapsed = range.collapsed; | 2027 | collapsed = range.collapsed; |
1793 | } | 2028 | } |
1794 | 2029 | ||
@@ -1830,18 +2065,18 @@ | |||
1830 | 2065 | ||
1831 | // Append a temporary <span></span> before the selection. | 2066 | // Append a temporary <span></span> before the selection. |
1832 | // This is needed to avoid IE destroying selections inside empty | 2067 | // This is needed to avoid IE destroying selections inside empty |
1833 | // inline elements, like <b></b> (#253). | 2068 | // inline elements, like <b></b> (http://dev.ckeditor.com/ticket/253). |
1834 | // It is also needed when placing the selection right after an inline | 2069 | // It is also needed when placing the selection right after an inline |
1835 | // element to avoid the selection moving inside of it. | 2070 | // element to avoid the selection moving inside of it. |
1836 | dummySpan = range.document.createElement( 'span' ); | 2071 | dummySpan = range.document.createElement( 'span' ); |
1837 | dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359. | 2072 | dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See http://dev.ckeditor.com/ticket/1359. |
1838 | dummySpan.insertBefore( startNode ); | 2073 | dummySpan.insertBefore( startNode ); |
1839 | 2074 | ||
1840 | if ( isStartMarkerAlone ) { | 2075 | if ( isStartMarkerAlone ) { |
1841 | // To expand empty blocks or line spaces after <br>, we need | 2076 | // To expand empty blocks or line spaces after <br>, we need |
1842 | // instead to have any char, which will be later deleted using the | 2077 | // instead to have any char, which will be later deleted using the |
1843 | // selection. | 2078 | // selection. |
1844 | // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) | 2079 | // \ufeff = Zero Width No-Break Space (U+FEFF). (http://dev.ckeditor.com/ticket/1359) |
1845 | range.document.createText( '\ufeff' ).insertBefore( startNode ); | 2080 | range.document.createText( '\ufeff' ).insertBefore( startNode ); |
1846 | } | 2081 | } |
1847 | } | 2082 | } |
@@ -1873,7 +2108,7 @@ | |||
1873 | } else { | 2108 | } else { |
1874 | var sel = this.getNative(); | 2109 | var sel = this.getNative(); |
1875 | 2110 | ||
1876 | // getNative() returns null if iframe is "display:none" in FF. (#6577) | 2111 | // getNative() returns null if iframe is "display:none" in FF. (http://dev.ckeditor.com/ticket/6577) |
1877 | if ( !sel ) | 2112 | if ( !sel ) |
1878 | return; | 2113 | return; |
1879 | 2114 | ||
@@ -1889,7 +2124,7 @@ | |||
1889 | between.setStart( left.endContainer, left.endOffset ); | 2124 | between.setStart( left.endContainer, left.endOffset ); |
1890 | between.setEnd( right.startContainer, right.startOffset ); | 2125 | between.setEnd( right.startContainer, right.startOffset ); |
1891 | 2126 | ||
1892 | // Don't confused by Firefox adjancent multi-ranges | 2127 | // Don't confused by Firefox adjacent multi-ranges |
1893 | // introduced by table cells selection. | 2128 | // introduced by table cells selection. |
1894 | if ( !between.collapsed ) { | 2129 | if ( !between.collapsed ) { |
1895 | between.shrink( CKEDITOR.NODE_ELEMENT, true ); | 2130 | between.shrink( CKEDITOR.NODE_ELEMENT, true ); |
@@ -1898,7 +2133,7 @@ | |||
1898 | 2133 | ||
1899 | // The following cases has to be considered: | 2134 | // The following cases has to be considered: |
1900 | // 1. <span contenteditable="false">[placeholder]</span> | 2135 | // 1. <span contenteditable="false">[placeholder]</span> |
1901 | // 2. <input contenteditable="false" type="radio"/> (#6621) | 2136 | // 2. <input contenteditable="false" type="radio"/> (http://dev.ckeditor.com/ticket/6621) |
1902 | if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) { | 2137 | if ( ancestor.isReadOnly() || enclosed && enclosed.isReadOnly() ) { |
1903 | right.setStart( left.startContainer, left.startOffset ); | 2138 | right.setStart( left.startContainer, left.startOffset ); |
1904 | ranges.splice( i--, 1 ); | 2139 | ranges.splice( i--, 1 ); |
@@ -1913,7 +2148,7 @@ | |||
1913 | 2148 | ||
1914 | if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { | 2149 | if ( range.collapsed && CKEDITOR.env.webkit && rangeRequiresFix( range ) ) { |
1915 | // Append a zero-width space so WebKit will not try to | 2150 | // Append a zero-width space so WebKit will not try to |
1916 | // move the selection by itself (#1272). | 2151 | // move the selection by itself (http://dev.ckeditor.com/ticket/1272). |
1917 | var fillingChar = createFillingCharSequenceNode( this.root ); | 2152 | var fillingChar = createFillingCharSequenceNode( this.root ); |
1918 | range.insertNode( fillingChar ); | 2153 | range.insertNode( fillingChar ); |
1919 | 2154 | ||
@@ -1972,7 +2207,7 @@ | |||
1972 | fake: function( element, ariaLabel ) { | 2207 | fake: function( element, ariaLabel ) { |
1973 | var editor = this.root.editor; | 2208 | var editor = this.root.editor; |
1974 | 2209 | ||
1975 | // Attempt to retreive aria-label if possible (#14539). | 2210 | // Attempt to retrieve aria-label if possible (http://dev.ckeditor.com/ticket/14539). |
1976 | if ( ariaLabel === undefined && element.hasAttribute( 'aria-label' ) ) { | 2211 | if ( ariaLabel === undefined && element.hasAttribute( 'aria-label' ) ) { |
1977 | ariaLabel = element.getAttribute( 'aria-label' ); | 2212 | ariaLabel = element.getAttribute( 'aria-label' ); |
1978 | } | 2213 | } |
@@ -2031,6 +2266,38 @@ | |||
2031 | }, | 2266 | }, |
2032 | 2267 | ||
2033 | /** | 2268 | /** |
2269 | * Checks if the selection contains an HTML element inside a table. | ||
2270 | * Returns `false` for text selection inside a table (e.g. it will return `false` | ||
2271 | * for text selected in one cell). | ||
2272 | * | ||
2273 | * editor.getSelection().isInTable(); | ||
2274 | * | ||
2275 | * @since 4.7.0 | ||
2276 | * @param {Boolean} [allowPartialSelection=false] Whether a partial cell selection should be included. | ||
2277 | * Added in 4.7.2. | ||
2278 | * @returns {Boolean} | ||
2279 | */ | ||
2280 | isInTable: function( allowPartialSelection ) { | ||
2281 | return isTableSelection( this.getRanges(), allowPartialSelection ); | ||
2282 | }, | ||
2283 | |||
2284 | /** | ||
2285 | * Checks if the selection contains only one range which is collapsed. | ||
2286 | * | ||
2287 | * if ( editor.getSelection().isCollapsed() ) { | ||
2288 | * // Do something when the selection is collapsed. | ||
2289 | * } | ||
2290 | * | ||
2291 | * @since 4.7.3 | ||
2292 | * @returns {Boolean} | ||
2293 | */ | ||
2294 | isCollapsed: function() { | ||
2295 | var ranges = this.getRanges(); | ||
2296 | |||
2297 | return ranges.length === 1 && ranges[ 0 ].collapsed; | ||
2298 | }, | ||
2299 | |||
2300 | /** | ||
2034 | * Creates a bookmark for each range of this selection (from {@link #getRanges}) | 2301 | * Creates a bookmark for each range of this selection (from {@link #getRanges}) |
2035 | * by calling the {@link CKEDITOR.dom.range#createBookmark} method, | 2302 | * by calling the {@link CKEDITOR.dom.range#createBookmark} method, |
2036 | * with extra care taken to avoid interference among those ranges. The arguments | 2303 | * with extra care taken to avoid interference among those ranges. The arguments |
@@ -2083,17 +2350,19 @@ | |||
2083 | 2350 | ||
2084 | // It may happen that the content change during loading, before selection is set so bookmark leads to text node. | 2351 | // It may happen that the content change during loading, before selection is set so bookmark leads to text node. |
2085 | if ( bookmarks.isFake ) { | 2352 | if ( bookmarks.isFake ) { |
2086 | node = ranges[ 0 ].getEnclosedNode(); | 2353 | node = isTableSelection( ranges ) ? ranges[ 0 ]._getTableElement() : ranges[ 0 ].getEnclosedNode(); |
2354 | |||
2087 | if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) { | 2355 | if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) { |
2088 | CKEDITOR.warn( 'selection-not-fake' ); | 2356 | CKEDITOR.warn( 'selection-not-fake' ); |
2089 | bookmarks.isFake = 0; | 2357 | bookmarks.isFake = 0; |
2090 | } | 2358 | } |
2091 | } | 2359 | } |
2092 | 2360 | ||
2093 | if ( bookmarks.isFake ) | 2361 | if ( bookmarks.isFake && !isTableSelection( ranges ) ) { |
2094 | this.fake( node ); | 2362 | this.fake( node ); |
2095 | else | 2363 | } else { |
2096 | this.selectRanges( ranges ); | 2364 | this.selectRanges( ranges ); |
2365 | } | ||
2097 | 2366 | ||
2098 | return this; | 2367 | return this; |
2099 | }, | 2368 | }, |
@@ -2130,7 +2399,7 @@ | |||
2130 | * Remove all the selection ranges from the document. | 2399 | * Remove all the selection ranges from the document. |
2131 | */ | 2400 | */ |
2132 | removeAllRanges: function() { | 2401 | removeAllRanges: function() { |
2133 | // Don't clear selection outside this selection's root (#11500). | 2402 | // Don't clear selection outside this selection's root (http://dev.ckeditor.com/ticket/11500). |
2134 | if ( this.getType() == CKEDITOR.SELECTION_NONE ) | 2403 | if ( this.getType() == CKEDITOR.SELECTION_NONE ) |
2135 | return; | 2404 | return; |
2136 | 2405 | ||
diff --git a/sources/core/skin.js b/sources/core/skin.js index 4f0ee7c..290157d 100644 --- a/sources/core/skin.js +++ b/sources/core/skin.js | |||
@@ -125,7 +125,7 @@ | |||
125 | offset = overrideOffset || ( icon && icon.offset ); | 125 | offset = overrideOffset || ( icon && icon.offset ); |
126 | bgsize = overrideBgsize || ( icon && icon.bgsize ) || '16px'; | 126 | bgsize = overrideBgsize || ( icon && icon.bgsize ) || '16px'; |
127 | 127 | ||
128 | // If we use apostrophes in background-image, we must escape apostrophes in path (just to be sure). (#13361) | 128 | // If we use apostrophes in background-image, we must escape apostrophes in path (just to be sure). (http://dev.ckeditor.com/ticket/13361) |
129 | if ( path ) | 129 | if ( path ) |
130 | path = path.replace( /'/g, '\\\'' ); | 130 | path = path.replace( /'/g, '\\\'' ); |
131 | 131 | ||
diff --git a/sources/core/style.js b/sources/core/style.js index efb9d7f..b3cf0bc 100644 --- a/sources/core/style.js +++ b/sources/core/style.js | |||
@@ -235,7 +235,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
235 | var initialEnterMode = this._.enterMode; | 235 | var initialEnterMode = this._.enterMode; |
236 | 236 | ||
237 | // Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode | 237 | // Before CKEditor 4.4 style knew nothing about editor, so in order to provide enterMode |
238 | // which should be used developers were forced to hack the style object (see #10190). | 238 | // which should be used developers were forced to hack the style object (see http://dev.ckeditor.com/ticket/10190). |
239 | // Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we | 239 | // Since CKEditor 4.4 style knows about editor (at least when it's being applied/removed), but we |
240 | // use _.enterMode for backward compatibility with those hacks. | 240 | // use _.enterMode for backward compatibility with those hacks. |
241 | // Note: we should not change style's enter mode if it was already set. | 241 | // Note: we should not change style's enter mode if it was already set. |
@@ -569,7 +569,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
569 | var styleVal = stylesDef[ style ], | 569 | var styleVal = stylesDef[ style ], |
570 | text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' ); | 570 | text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' ); |
571 | 571 | ||
572 | // Some browsers don't support 'inherit' property value, leave them intact. (#5242) | 572 | // Some browsers don't support 'inherit' property value, leave them intact. (http://dev.ckeditor.com/ticket/5242) |
573 | if ( styleVal == 'inherit' ) | 573 | if ( styleVal == 'inherit' ) |
574 | specialStylesText += text; | 574 | specialStylesText += text; |
575 | else | 575 | else |
@@ -1024,7 +1024,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1024 | if ( !CKEDITOR.env.ie ) | 1024 | if ( !CKEDITOR.env.ie ) |
1025 | styleNode.$.normalize(); | 1025 | styleNode.$.normalize(); |
1026 | } | 1026 | } |
1027 | // Style already inherit from parents, left just to clear up any internal overrides. (#5931) | 1027 | // Style already inherit from parents, left just to clear up any internal overrides. (http://dev.ckeditor.com/ticket/5931) |
1028 | else { | 1028 | else { |
1029 | styleNode = new CKEDITOR.dom.element( 'span' ); | 1029 | styleNode = new CKEDITOR.dom.element( 'span' ); |
1030 | styleRange.extractContents().appendTo( styleNode ); | 1030 | styleRange.extractContents().appendTo( styleNode ); |
@@ -1042,7 +1042,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1042 | // Remove the bookmark nodes. | 1042 | // Remove the bookmark nodes. |
1043 | range.moveToBookmark( boundaryNodes ); | 1043 | range.moveToBookmark( boundaryNodes ); |
1044 | 1044 | ||
1045 | // Minimize the result range to exclude empty text nodes. (#5374) | 1045 | // Minimize the result range to exclude empty text nodes. (http://dev.ckeditor.com/ticket/5374) |
1046 | range.shrink( CKEDITOR.SHRINK_TEXT ); | 1046 | range.shrink( CKEDITOR.SHRINK_TEXT ); |
1047 | 1047 | ||
1048 | // Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside. | 1048 | // Get inside the remaining element if range.shrink( TEXT ) has failed because of non-editable elements inside. |
@@ -1058,27 +1058,31 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1058 | range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 ); | 1058 | range.enlarge( CKEDITOR.ENLARGE_INLINE, 1 ); |
1059 | 1059 | ||
1060 | var bookmark = range.createBookmark(), | 1060 | var bookmark = range.createBookmark(), |
1061 | startNode = bookmark.startNode; | 1061 | startNode = bookmark.startNode, |
1062 | alwaysRemoveElement = this._.definition.alwaysRemoveElement; | ||
1062 | 1063 | ||
1063 | if ( range.collapsed ) { | 1064 | if ( range.collapsed ) { |
1064 | var startPath = new CKEDITOR.dom.elementPath( startNode.getParent(), range.root ), | 1065 | var startPath = new CKEDITOR.dom.elementPath( startNode.getParent(), range.root ), |
1065 | // The topmost element in elementspatch which we should jump out of. | 1066 | // The topmost element in elements path which we should jump out of. |
1066 | boundaryElement; | 1067 | boundaryElement; |
1067 | 1068 | ||
1068 | |||
1069 | for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) { | 1069 | for ( var i = 0, element; i < startPath.elements.length && ( element = startPath.elements[ i ] ); i++ ) { |
1070 | // 1. If it's collaped inside text nodes, try to remove the style from the whole element. | 1070 | // 1. If it's collaped inside text nodes, try to remove the style from the whole element. |
1071 | // | 1071 | // |
1072 | // 2. Otherwise if it's collapsed on element boundaries, moving the selection | 1072 | // 2. Otherwise if it's collapsed on element boundaries, moving the selection |
1073 | // outside the styles instead of removing the whole tag, | 1073 | // outside the styles instead of removing the whole tag, |
1074 | // also make sure other inner styles were well preserverd.(#3309) | 1074 | // also make sure other inner styles were well preserved.(http://dev.ckeditor.com/ticket/3309) |
1075 | if ( element == startPath.block || element == startPath.blockLimit ) | 1075 | // |
1076 | // 3. Force removing the element even if it's an boundary element when alwaysRemoveElement is true. | ||
1077 | // Without it, the links won't be unlinked if the cursor is placed right before/after it. (http://dev.ckeditor.com/ticket/13062) | ||
1078 | if ( element == startPath.block || element == startPath.blockLimit ) { | ||
1076 | break; | 1079 | break; |
1080 | } | ||
1077 | 1081 | ||
1078 | if ( this.checkElementRemovable( element ) ) { | 1082 | if ( this.checkElementRemovable( element ) ) { |
1079 | var isStart; | 1083 | var isStart; |
1080 | 1084 | ||
1081 | if ( range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) { | 1085 | if ( !alwaysRemoveElement && range.collapsed && ( range.checkBoundaryOfElement( element, CKEDITOR.END ) || ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) ) { |
1082 | boundaryElement = element; | 1086 | boundaryElement = element; |
1083 | boundaryElement.match = isStart ? 'start' : 'end'; | 1087 | boundaryElement.match = isStart ? 'start' : 'end'; |
1084 | } else { | 1088 | } else { |
@@ -1087,10 +1091,11 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1087 | // no difference that they're separate entities in the DOM tree. So, merge | 1091 | // no difference that they're separate entities in the DOM tree. So, merge |
1088 | // them before removal. | 1092 | // them before removal. |
1089 | element.mergeSiblings(); | 1093 | element.mergeSiblings(); |
1090 | if ( element.is( this.element ) ) | 1094 | if ( element.is( this.element ) ) { |
1091 | removeFromElement.call( this, element ); | 1095 | removeFromElement.call( this, element ); |
1092 | else | 1096 | } else { |
1093 | removeOverrides( element, getOverrides( this )[ element.getName() ] ); | 1097 | removeOverrides( element, getOverrides( this )[ element.getName() ] ); |
1098 | } | ||
1094 | } | 1099 | } |
1095 | } | 1100 | } |
1096 | } | 1101 | } |
@@ -1235,7 +1240,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1235 | } | 1240 | } |
1236 | 1241 | ||
1237 | function applyObjectStyle( range ) { | 1242 | function applyObjectStyle( range ) { |
1238 | // Selected or parent element. (#9651) | 1243 | // Selected or parent element. (http://dev.ckeditor.com/ticket/9651) |
1239 | var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ), | 1244 | var start = range.getEnclosedNode() || range.getCommonAncestor( false, true ), |
1240 | element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 ); | 1245 | element = new CKEDITOR.dom.elementPath( start, range.root ).contains( this.element, 1 ); |
1241 | 1246 | ||
@@ -1276,7 +1281,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1276 | var iterator = range.createIterator(); | 1281 | var iterator = range.createIterator(); |
1277 | iterator.enforceRealBlocks = true; | 1282 | iterator.enforceRealBlocks = true; |
1278 | 1283 | ||
1279 | // make recognize <br /> tag as a separator in ENTER_BR mode (#5121) | 1284 | // make recognize <br /> tag as a separator in ENTER_BR mode (http://dev.ckeditor.com/ticket/5121) |
1280 | if ( this._.enterMode ) | 1285 | if ( this._.enterMode ) |
1281 | iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR ); | 1286 | iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR ); |
1282 | 1287 | ||
@@ -1326,7 +1331,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1326 | 1331 | ||
1327 | // Replace the original block with new one, with special treatment | 1332 | // Replace the original block with new one, with special treatment |
1328 | // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent | 1333 | // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent |
1329 | // when necessary. (#3188) | 1334 | // when necessary. (http://dev.ckeditor.com/ticket/3188) |
1330 | function replaceBlock( block, newBlock ) { | 1335 | function replaceBlock( block, newBlock ) { |
1331 | // Block is to be removed, create a temp element to | 1336 | // Block is to be removed, create a temp element to |
1332 | // save contents. | 1337 | // save contents. |
@@ -1502,11 +1507,11 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1502 | 1507 | ||
1503 | // Remove definition attributes/style from the elemnt. | 1508 | // Remove definition attributes/style from the elemnt. |
1504 | for ( var attName in attributes ) { | 1509 | for ( var attName in attributes ) { |
1505 | // The 'class' element value must match (#1318). | 1510 | // The 'class' element value must match (http://dev.ckeditor.com/ticket/1318). |
1506 | if ( ( attName == 'class' || this._.definition.fullMatch ) && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) ) | 1511 | if ( ( attName == 'class' || this._.definition.fullMatch ) && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) ) |
1507 | continue; | 1512 | continue; |
1508 | 1513 | ||
1509 | // Do not touch data-* attributes (#11011) (#11258). | 1514 | // Do not touch data-* attributes (http://dev.ckeditor.com/ticket/11011) (http://dev.ckeditor.com/ticket/11258). |
1510 | if ( keepDataAttrs && attName.slice( 0, 5 ) == 'data-' ) | 1515 | if ( keepDataAttrs && attName.slice( 0, 5 ) == 'data-' ) |
1511 | continue; | 1516 | continue; |
1512 | 1517 | ||
@@ -1515,7 +1520,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1515 | } | 1520 | } |
1516 | 1521 | ||
1517 | for ( var styleName in styles ) { | 1522 | for ( var styleName in styles ) { |
1518 | // Full match style insist on having fully equivalence. (#5018) | 1523 | // Full match style insist on having fully equivalence. (http://dev.ckeditor.com/ticket/5018) |
1519 | if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) ) | 1524 | if ( this._.definition.fullMatch && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) ) |
1520 | continue; | 1525 | continue; |
1521 | 1526 | ||
@@ -1649,7 +1654,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1649 | // Create the element. | 1654 | // Create the element. |
1650 | el = new CKEDITOR.dom.element( elementName, targetDocument ); | 1655 | el = new CKEDITOR.dom.element( elementName, targetDocument ); |
1651 | 1656 | ||
1652 | // #6226: attributes should be copied before the new ones are applied | 1657 | // http://dev.ckeditor.com/ticket/6226: attributes should be copied before the new ones are applied |
1653 | if ( element ) | 1658 | if ( element ) |
1654 | element.copyAttributes( el ); | 1659 | element.copyAttributes( el ); |
1655 | 1660 | ||
@@ -1797,7 +1802,7 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1797 | // @returns {Boolean} | 1802 | // @returns {Boolean} |
1798 | function compareCssText( source, target ) { | 1803 | function compareCssText( source, target ) { |
1799 | function filter( string, propertyName ) { | 1804 | function filter( string, propertyName ) { |
1800 | // In case of font-families we'll skip quotes. (#10750) | 1805 | // In case of font-families we'll skip quotes. (http://dev.ckeditor.com/ticket/10750) |
1801 | return propertyName.toLowerCase() == 'font-family' ? string.replace( /["']/g, '' ) : string; | 1806 | return propertyName.toLowerCase() == 'font-family' ? string.replace( /["']/g, '' ) : string; |
1802 | } | 1807 | } |
1803 | 1808 | ||
@@ -1824,13 +1829,25 @@ CKEDITOR.STYLE_OBJECT = 3; | |||
1824 | var doc = selection.document, | 1829 | var doc = selection.document, |
1825 | ranges = selection.getRanges(), | 1830 | ranges = selection.getRanges(), |
1826 | func = remove ? this.removeFromRange : this.applyToRange, | 1831 | func = remove ? this.removeFromRange : this.applyToRange, |
1827 | range; | 1832 | originalRanges, |
1833 | range, | ||
1834 | i; | ||
1835 | |||
1836 | // In case of fake table selection, we would like to apply all styles and then select | ||
1837 | // the original ranges. Otherwise browsers would complain about discontiguous selection. | ||
1838 | if ( selection.isFake && selection.isInTable() ) { | ||
1839 | originalRanges = []; | ||
1840 | |||
1841 | for ( i = 0; i < ranges.length; i++ ) { | ||
1842 | originalRanges.push( ranges[ i ].clone() ); | ||
1843 | } | ||
1844 | } | ||
1828 | 1845 | ||
1829 | var iterator = ranges.createIterator(); | 1846 | var iterator = ranges.createIterator(); |
1830 | while ( ( range = iterator.getNextRange() ) ) | 1847 | while ( ( range = iterator.getNextRange() ) ) |
1831 | func.call( this, range, editor ); | 1848 | func.call( this, range, editor ); |
1832 | 1849 | ||
1833 | selection.selectRanges( ranges ); | 1850 | selection.selectRanges( originalRanges || ranges ); |
1834 | doc.removeCustomData( 'doc_processing_style' ); | 1851 | doc.removeCustomData( 'doc_processing_style' ); |
1835 | } | 1852 | } |
1836 | } )(); | 1853 | } )(); |
@@ -1901,7 +1918,7 @@ CKEDITOR.styleCommand.prototype.exec = function( editor ) { | |||
1901 | */ | 1918 | */ |
1902 | CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' ); | 1919 | CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' ); |
1903 | 1920 | ||
1904 | // Backward compatibility (#5025). | 1921 | // Backward compatibility (http://dev.ckeditor.com/ticket/5025). |
1905 | CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet ); | 1922 | CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet ); |
1906 | CKEDITOR.loadStylesSet = function( name, url, callback ) { | 1923 | CKEDITOR.loadStylesSet = function( name, url, callback ) { |
1907 | CKEDITOR.stylesSet.addExternal( name, url, '' ); | 1924 | CKEDITOR.stylesSet.addExternal( name, url, '' ); |
@@ -2004,7 +2021,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, { | |||
2004 | return; | 2021 | return; |
2005 | } | 2022 | } |
2006 | 2023 | ||
2007 | // #5352 Allow to define the styles directly in the config object | 2024 | // http://dev.ckeditor.com/ticket/5352 Allow to define the styles directly in the config object |
2008 | if ( configStyleSet instanceof Array ) { | 2025 | if ( configStyleSet instanceof Array ) { |
2009 | editor._.stylesDefinitions = configStyleSet; | 2026 | editor._.stylesDefinitions = configStyleSet; |
2010 | callback( configStyleSet ); | 2027 | callback( configStyleSet ); |
diff --git a/sources/core/template.js b/sources/core/template.js index 2b9e932..a627c34 100644 --- a/sources/core/template.js +++ b/sources/core/template.js | |||
@@ -9,11 +9,7 @@ | |||
9 | */ | 9 | */ |
10 | 10 | ||
11 | ( function() { | 11 | ( function() { |
12 | var cache = {}, | 12 | var rePlaceholder = /{([^}]+)}/g; |
13 | rePlaceholder = /{([^}]+)}/g, | ||
14 | reEscapableChars = /([\\'])/g, | ||
15 | reNewLine = /\n/g, | ||
16 | reCarriageReturn = /\r/g; | ||
17 | 13 | ||
18 | /** | 14 | /** |
19 | * Lightweight template used to build the output string from variables. | 15 | * Lightweight template used to build the output string from variables. |
@@ -27,42 +23,35 @@ | |||
27 | * @param {String} source The template source. | 23 | * @param {String} source The template source. |
28 | */ | 24 | */ |
29 | CKEDITOR.template = function( source ) { | 25 | CKEDITOR.template = function( source ) { |
30 | // Builds an optimized function body for the output() method, focused on performance. | 26 | /** |
31 | // For example, if we have this "source": | 27 | * The current template source. |
32 | // '<div style="{style}">{editorName}</div>' | 28 | * |
33 | // ... the resulting function body will be (apart from the "buffer" handling): | 29 | * @readonly |
34 | // return [ '<div style="', data['style'] == undefined ? '{style}' : data['style'], '">', data['editorName'] == undefined ? '{editorName}' : data['editorName'], '</div>' ].join(''); | 30 | * @member CKEDITOR.template |
31 | * @property {String} | ||
32 | */ | ||
33 | this.source = String( source ); | ||
34 | }; | ||
35 | 35 | ||
36 | // Try to read from the cache. | 36 | /** |
37 | if ( cache[ source ] ) | 37 | * Processes the template, filling its variables with the provided data. |
38 | this.output = cache[ source ]; | 38 | * |
39 | else { | 39 | * @method |
40 | var fn = source | 40 | * @member CKEDITOR.template |
41 | // Escape chars like slash "\" or single quote "'". | 41 | * @param {Object} data An object containing properties whose values will be |
42 | .replace( reEscapableChars, '\\$1' ) | 42 | * used to fill the template variables. The property names must match the |
43 | .replace( reNewLine, '\\n' ) | 43 | * template variables names. Variables without matching properties will be |
44 | .replace( reCarriageReturn, '\\r' ) | 44 | * kept untouched. |
45 | // Inject the template keys replacement. | 45 | * @param {Array} [buffer] An array that the output data will be pushed into. |
46 | .replace( rePlaceholder, function( m, key ) { | 46 | * The number of entries appended to the array is unknown. |
47 | return "',data['" + key + "']==undefined?'{" + key + "}':data['" + key + "'],'"; | 47 | * @returns {String/Number} If `buffer` has not been provided, the processed |
48 | } ); | 48 | * template output data; otherwise the new length of `buffer`. |
49 | */ | ||
50 | CKEDITOR.template.prototype.output = function( data, buffer ) { | ||
51 | var output = this.source.replace( rePlaceholder, function( fullMatch, dataKey ) { | ||
52 | return data[ dataKey ] !== undefined ? data[ dataKey ] : fullMatch; | ||
53 | } ); | ||
49 | 54 | ||
50 | fn = "return buffer?buffer.push('" + fn + "'):['" + fn + "'].join('');"; | 55 | return buffer ? buffer.push( output ) : output; |
51 | this.output = cache[ source ] = Function( 'data', 'buffer', fn ); | ||
52 | } | ||
53 | }; | 56 | }; |
54 | } )(); | 57 | } )(); |
55 | |||
56 | /** | ||
57 | * Processes the template, filling its variables with the provided data. | ||
58 | * | ||
59 | * @method output | ||
60 | * @param {Object} data An object containing properties which values will be | ||
61 | * used to fill the template variables. The property names must match the | ||
62 | * template variables names. Variables without matching properties will be | ||
63 | * kept untouched. | ||
64 | * @param {Array} [buffer] An array into which the output data will be pushed into. | ||
65 | * The number of entries appended to the array is unknown. | ||
66 | * @returns {String/Number} If `buffer` has not been provided, the processed | ||
67 | * template output data, otherwise the new length of `buffer`. | ||
68 | */ | ||
diff --git a/sources/core/tools.js b/sources/core/tools.js index 7e0083f..a4b736d 100644 --- a/sources/core/tools.js +++ b/sources/core/tools.js | |||
@@ -1021,7 +1021,7 @@ | |||
1021 | styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) ); | 1021 | styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) ); |
1022 | } | 1022 | } |
1023 | 1023 | ||
1024 | // IE will leave a single semicolon when failed to parse the style text. (#3891) | 1024 | // IE will leave a single semicolon when failed to parse the style text. (http://dev.ckeditor.com/ticket/3891) |
1025 | if ( !styleText || styleText == ';' ) | 1025 | if ( !styleText || styleText == ';' ) |
1026 | return retval; | 1026 | return retval; |
1027 | 1027 | ||
@@ -1463,6 +1463,37 @@ | |||
1463 | }, | 1463 | }, |
1464 | 1464 | ||
1465 | /** | 1465 | /** |
1466 | * Detects which mouse button generated a given DOM event. | ||
1467 | * | ||
1468 | * @since 4.7.3 | ||
1469 | * @param {CKEDITOR.dom.event} evt DOM event. | ||
1470 | * @returns {Number|Boolean} Returns a number indicating the mouse button or `false` | ||
1471 | * if the mouse button cannot be determined. | ||
1472 | */ | ||
1473 | getMouseButton: function( evt ) { | ||
1474 | var evtData = evt.data, | ||
1475 | domEvent = evtData && evtData.$; | ||
1476 | |||
1477 | if ( !( evtData && domEvent ) ) { | ||
1478 | // Added in case when there's no data available. That's the case in some unit test in built version which | ||
1479 | // mock event but doesn't put data object. | ||
1480 | return false; | ||
1481 | } | ||
1482 | |||
1483 | if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { | ||
1484 | if ( domEvent.button === 4 ) { | ||
1485 | return CKEDITOR.MOUSE_BUTTON_MIDDLE; | ||
1486 | } else if ( domEvent.button === 1 ) { | ||
1487 | return CKEDITOR.MOUSE_BUTTON_LEFT; | ||
1488 | } else { | ||
1489 | return CKEDITOR.MOUSE_BUTTON_RIGHT; | ||
1490 | } | ||
1491 | } | ||
1492 | |||
1493 | return domEvent.button; | ||
1494 | }, | ||
1495 | |||
1496 | /** | ||
1466 | * A set of functions for operations on styles. | 1497 | * A set of functions for operations on styles. |
1467 | * | 1498 | * |
1468 | * @property {CKEDITOR.tools.style} | 1499 | * @property {CKEDITOR.tools.style} |
@@ -1627,6 +1658,21 @@ | |||
1627 | yellowgreen: '#9ACD32' | 1658 | yellowgreen: '#9ACD32' |
1628 | }, | 1659 | }, |
1629 | 1660 | ||
1661 | _borderStyle: [ | ||
1662 | 'none', | ||
1663 | 'hidden', | ||
1664 | 'dotted', | ||
1665 | 'dashed', | ||
1666 | 'solid', | ||
1667 | 'double', | ||
1668 | 'groove', | ||
1669 | 'ridge', | ||
1670 | 'inset', | ||
1671 | 'outset' | ||
1672 | ], | ||
1673 | |||
1674 | _widthRegExp: /^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/, | ||
1675 | |||
1630 | _rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi, | 1676 | _rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi, |
1631 | 1677 | ||
1632 | _hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi, | 1678 | _hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi, |
@@ -1647,10 +1693,8 @@ | |||
1647 | * @member CKEDITOR.tools.style.parse | 1693 | * @member CKEDITOR.tools.style.parse |
1648 | */ | 1694 | */ |
1649 | background: function( value ) { | 1695 | background: function( value ) { |
1650 | var ret = [], | 1696 | var ret = {}, |
1651 | colors = []; | 1697 | colors = this._findColor( value ); |
1652 | |||
1653 | colors = this._findColor( value ); | ||
1654 | 1698 | ||
1655 | if ( colors.length ) { | 1699 | if ( colors.length ) { |
1656 | ret.color = colors[ 0 ]; | 1700 | ret.color = colors[ 0 ]; |
@@ -1715,6 +1759,51 @@ | |||
1715 | }, | 1759 | }, |
1716 | 1760 | ||
1717 | /** | 1761 | /** |
1762 | * Parses the `border` CSS property shorthand format. | ||
1763 | * This CSS property does not support inheritance (https://www.w3.org/TR/css3-background/#the-border-shorthands). | ||
1764 | * | ||
1765 | * console.log( CKEDITOR.tools.style.parse.border( '3px solid #ffeedd' ) ); | ||
1766 | * // Logs: { width: "3px", style: "solid", color: "#ffeedd" } | ||
1767 | * | ||
1768 | * @param {String} value The `border` property value. | ||
1769 | * @returns {Object} | ||
1770 | * @returns {String} return.width The border-width attribute. | ||
1771 | * @returns {String} return.style The border-style attribute. | ||
1772 | * @returns {String} return.color The border-color attribute. | ||
1773 | * @member CKEDITOR.tools.style.parse | ||
1774 | */ | ||
1775 | border: function( value ) { | ||
1776 | var ret = {}, | ||
1777 | input = value.split( /\s+/ ); | ||
1778 | |||
1779 | CKEDITOR.tools.array.forEach( input, function( val ) { | ||
1780 | if ( !ret.color ) { | ||
1781 | var parseColor = CKEDITOR.tools.style.parse._findColor( val ); | ||
1782 | if ( parseColor.length ) { | ||
1783 | ret.color = parseColor[ 0 ]; | ||
1784 | return; | ||
1785 | } | ||
1786 | } | ||
1787 | |||
1788 | if ( !ret.style ) { | ||
1789 | if ( CKEDITOR.tools.indexOf( CKEDITOR.tools.style.parse._borderStyle, val ) !== -1 ) { | ||
1790 | ret.style = val; | ||
1791 | return; | ||
1792 | } | ||
1793 | } | ||
1794 | |||
1795 | if ( !ret.width ) { | ||
1796 | if ( CKEDITOR.tools.style.parse._widthRegExp.test( val ) ) { | ||
1797 | ret.width = val; | ||
1798 | return; | ||
1799 | } | ||
1800 | } | ||
1801 | |||
1802 | } ); | ||
1803 | return ret; | ||
1804 | }, | ||
1805 | |||
1806 | /** | ||
1718 | * Searches the `value` for any CSS color occurrences and returns it. | 1807 | * Searches the `value` for any CSS color occurrences and returns it. |
1719 | * | 1808 | * |
1720 | * @private | 1809 | * @private |
@@ -1846,6 +1935,39 @@ | |||
1846 | } | 1935 | } |
1847 | return acc; | 1936 | return acc; |
1848 | } | 1937 | } |
1938 | }, | ||
1939 | |||
1940 | /** | ||
1941 | * A set of object helpers. | ||
1942 | * | ||
1943 | * @property {CKEDITOR.tools.object} | ||
1944 | * @member CKEDITOR.tools | ||
1945 | */ | ||
1946 | object: { | ||
1947 | /** | ||
1948 | * Returns the first key from `obj` which has a given `value`. | ||
1949 | * | ||
1950 | * @param {Object} obj An object whose `key` is looked for. | ||
1951 | * @param {Mixed} value An object's `value` to be looked for. | ||
1952 | * @returns {String/null} Matched `key` or `null` if not found. | ||
1953 | * @member CKEDITOR.tools.object | ||
1954 | */ | ||
1955 | |||
1956 | findKey: function( obj, value ) { | ||
1957 | if ( typeof obj !== 'object' ) { | ||
1958 | return null; | ||
1959 | } | ||
1960 | |||
1961 | var key; | ||
1962 | |||
1963 | for ( key in obj ) { | ||
1964 | if ( obj[ key ] === value ) { | ||
1965 | return key; | ||
1966 | } | ||
1967 | } | ||
1968 | |||
1969 | return null; | ||
1970 | } | ||
1849 | } | 1971 | } |
1850 | }; | 1972 | }; |
1851 | 1973 | ||
@@ -1889,7 +2011,35 @@ | |||
1889 | */ | 2011 | */ |
1890 | CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray; | 2012 | CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray; |
1891 | 2013 | ||
2014 | /** | ||
2015 | * Left mouse button. | ||
2016 | * | ||
2017 | * @since 4.7.3 | ||
2018 | * @readonly | ||
2019 | * @property {Number} [=0] | ||
2020 | * @member CKEDITOR | ||
2021 | */ | ||
2022 | CKEDITOR.MOUSE_BUTTON_LEFT = 0; | ||
1892 | 2023 | ||
2024 | /** | ||
2025 | * Middle mouse button. | ||
2026 | * | ||
2027 | * @since 4.7.3 | ||
2028 | * @readonly | ||
2029 | * @property {Number} [=1] | ||
2030 | * @member CKEDITOR | ||
2031 | */ | ||
2032 | CKEDITOR.MOUSE_BUTTON_MIDDLE = 1; | ||
2033 | |||
2034 | /** | ||
2035 | * Right mouse button. | ||
2036 | * | ||
2037 | * @since 4.7.3 | ||
2038 | * @readonly | ||
2039 | * @property {Number} [=2] | ||
2040 | * @member CKEDITOR | ||
2041 | */ | ||
2042 | CKEDITOR.MOUSE_BUTTON_RIGHT = 2; | ||
1893 | 2043 | ||
1894 | /** | 2044 | /** |
1895 | * The namespace containing functions to work on CSS properties. | 2045 | * The namespace containing functions to work on CSS properties. |
@@ -1911,6 +2061,13 @@ | |||
1911 | * @since 4.6.1 | 2061 | * @since 4.6.1 |
1912 | * @class CKEDITOR.tools.array | 2062 | * @class CKEDITOR.tools.array |
1913 | */ | 2063 | */ |
2064 | |||
2065 | /** | ||
2066 | * The namespace with helper functions and polyfills for objects. | ||
2067 | * | ||
2068 | * @since 4.7.1 | ||
2069 | * @class CKEDITOR.tools.object | ||
2070 | */ | ||
1914 | } )(); | 2071 | } )(); |
1915 | 2072 | ||
1916 | // PACKAGER_RENAME( CKEDITOR.tools ) | 2073 | // PACKAGER_RENAME( CKEDITOR.tools ) |