diff options
Diffstat (limited to 'sources/core/editor.js')
-rw-r--r-- | sources/core/editor.js | 120 |
1 files changed, 94 insertions, 26 deletions
diff --git a/sources/core/editor.js b/sources/core/editor.js index 31188d2..640cec0 100644 --- a/sources/core/editor.js +++ b/sources/core/editor.js | |||
@@ -1,5 +1,5 @@ | |||
1 | /** | 1 | /** |
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. | 2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | 3 | * For licensing, see LICENSE.md or http://ckeditor.com/license |
4 | */ | 4 | */ |
5 | 5 | ||
@@ -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; |
@@ -1324,6 +1352,38 @@ | |||
1324 | }, | 1352 | }, |
1325 | 1353 | ||
1326 | /** | 1354 | /** |
1355 | * Returns the keystroke that is assigned to a specified {@link CKEDITOR.command}. If no keystroke is assigned, | ||
1356 | * it returns `null`. | ||
1357 | * | ||
1358 | * Since version 4.7.0 this function also accepts a `command` parameter as a string. | ||
1359 | * | ||
1360 | * @since 4.6.0 | ||
1361 | * @param {CKEDITOR.command/String} command The {@link CKEDITOR.command} instance or a string with the command name. | ||
1362 | * @returns {Number/null} The keystroke assigned to the provided command or `null` if there is no keystroke. | ||
1363 | */ | ||
1364 | getCommandKeystroke: function( command ) { | ||
1365 | var commandInstance = ( typeof command === 'string' ? this.getCommand( command ) : command ); | ||
1366 | |||
1367 | if ( commandInstance ) { | ||
1368 | var commandName = CKEDITOR.tools.object.findKey( this.commands, commandInstance ), | ||
1369 | keystrokes = this.keystrokeHandler.keystrokes, | ||
1370 | key; | ||
1371 | |||
1372 | // Some commands have a fake keystroke - for example CUT/COPY/PASTE commands are handled natively. | ||
1373 | if ( commandInstance.fakeKeystroke ) { | ||
1374 | return commandInstance.fakeKeystroke; | ||
1375 | } | ||
1376 | |||
1377 | for ( key in keystrokes ) { | ||
1378 | if ( keystrokes.hasOwnProperty( key ) && keystrokes[ key ] == commandName ) { | ||
1379 | return key; | ||
1380 | } | ||
1381 | } | ||
1382 | } | ||
1383 | return null; | ||
1384 | }, | ||
1385 | |||
1386 | /** | ||
1327 | * Shorthand for {@link CKEDITOR.filter#addFeature}. | 1387 | * Shorthand for {@link CKEDITOR.filter#addFeature}. |
1328 | * | 1388 | * |
1329 | * @since 4.1 | 1389 | * @since 4.1 |
@@ -1512,7 +1572,7 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3; | |||
1512 | * @member CKEDITOR.config | 1572 | * @member CKEDITOR.config |
1513 | */ | 1573 | */ |
1514 | 1574 | ||
1515 | /** | 1575 | /** |
1516 | * 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 |
1517 | * 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), |
1518 | * 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. |
@@ -1811,6 +1871,14 @@ CKEDITOR.ELEMENT_MODE_INLINE = 3; | |||
1811 | */ | 1871 | */ |
1812 | 1872 | ||
1813 | /** | 1873 | /** |
1874 | * Event fired when the {@link #method-destroy} method is called, | ||
1875 | * but before destroying the editor. | ||
1876 | * | ||
1877 | * @event beforeDestroy | ||
1878 | * @param {CKEDITOR.editor} editor This editor instance. | ||
1879 | */ | ||
1880 | |||
1881 | /** | ||
1814 | * Internal event to get the current data. | 1882 | * Internal event to get the current data. |
1815 | * | 1883 | * |
1816 | * @event beforeGetData | 1884 | * @event beforeGetData |