| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- /**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
-
- /**
- * @fileOverview A plugin created to handle ticket #11064. While the issue is caused by native WebKit/Blink behaviour,
- * this plugin can be easily detached or modified when the issue is fixed in the browsers without changing the core.
- * When Ctrl/Cmd + A is pressed to select all content it does not work due to a bug in
- * Webkit/Blink if a non-editable element is at the beginning or the end of the content.
- */
-
- ( function() {
- 'use strict';
-
- CKEDITOR.plugins.add( 'widgetselection', {
-
- init: function( editor ) {
- if ( CKEDITOR.env.webkit ) {
- var widgetselection = CKEDITOR.plugins.widgetselection;
-
- editor.on( 'contentDom', function( evt ) {
-
- var editor = evt.editor,
- doc = editor.document,
- editable = editor.editable();
-
- editable.attachListener( doc, 'keydown', function( evt ) {
- var data = evt.data.$;
-
- // Ctrl/Cmd + A
- if ( evt.data.getKey() == 65 && ( CKEDITOR.env.mac && data.metaKey || !CKEDITOR.env.mac && data.ctrlKey ) ) {
-
- // Defer the call so the selection is already changed by the pressed keys.
- CKEDITOR.tools.setTimeout( function() {
-
- // Manage filler elements on keydown. If there is no need
- // to add fillers, we need to check and clean previously used once.
- if ( !widgetselection.addFillers( editable ) ) {
- widgetselection.removeFillers( editable );
- }
- }, 0 );
- }
- }, null, null, -1 );
-
- // Check and clean previously used fillers.
- editor.on( 'selectionCheck', function( evt ) {
- widgetselection.removeFillers( evt.editor.editable() );
- } );
-
- // Remove fillers on paste before data gets inserted into editor.
- editor.on( 'paste', function( evt ) {
- evt.data.dataValue = widgetselection.cleanPasteData( evt.data.dataValue );
- } );
-
- if ( 'selectall' in editor.plugins ) {
- widgetselection.addSelectAllIntegration( editor );
- }
- } );
- }
- }
- } );
-
- /**
- * A set of helper methods for the Widget Selection plugin.
- *
- * @property widgetselection
- * @member CKEDITOR.plugins
- * @since 4.6.1
- */
- CKEDITOR.plugins.widgetselection = {
-
- /**
- * The start filler element reference.
- *
- * @property {CKEDITOR.dom.element}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- startFiller: null,
-
- /**
- * The end filler element reference.
- *
- * @property {CKEDITOR.dom.element}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- endFiller: null,
-
- /**
- * An attribute which identifies the filler element.
- *
- * @property {String}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- fillerAttribute: 'data-cke-filler-webkit',
-
- /**
- * The default content of the filler element. Note: The filler needs to have `visible` content.
- * Unprintable elements or empty content do not help as a workaround.
- *
- * @property {String}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- fillerContent: ' ',
-
- /**
- * Tag name which is used to create fillers.
- *
- * @property {String}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- fillerTagName: 'div',
-
- /**
- * Adds a filler before or after a non-editable element at the beginning or the end of the `editable`.
- *
- * @param {CKEDITOR.editable} editable
- * @returns {Boolean}
- * @member CKEDITOR.plugins.widgetselection
- */
- addFillers: function( editable ) {
- var editor = editable.editor;
-
- // Whole content should be selected, if not fix the selection manually.
- if ( !this.isWholeContentSelected( editable ) && editable.getChildCount() > 0 ) {
-
- var firstChild = editable.getFirst( filterTempElements ),
- lastChild = editable.getLast( filterTempElements );
-
- // Check if first element is editable. If not prepend with filler.
- if ( firstChild && firstChild.type == CKEDITOR.NODE_ELEMENT && !firstChild.isEditable() ) {
- this.startFiller = this.createFiller();
- editable.append( this.startFiller, 1 );
- }
-
- // Check if last element is editable. If not append filler.
- if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && !lastChild.isEditable() ) {
- this.endFiller = this.createFiller( true );
- editable.append( this.endFiller, 0 );
- }
-
- // Reselect whole content after any filler was added.
- if ( this.hasFiller( editable ) ) {
- var rangeAll = editor.createRange();
- rangeAll.selectNodeContents( editable );
- rangeAll.select();
- return true;
- }
- }
- return false;
- },
-
- /**
- * Removes filler elements or updates their references.
- *
- * It will **not remove** filler elements if the whole content is selected, as it would break the
- * selection.
- *
- * @param {CKEDITOR.editable} editable
- * @member CKEDITOR.plugins.widgetselection
- */
- removeFillers: function( editable ) {
- // If startFiller or endFiller exists and not entire content is selected it means the selection
- // just changed from selected all. We need to remove fillers and set proper selection/content.
- if ( this.hasFiller( editable ) && !this.isWholeContentSelected( editable ) ) {
-
- var startFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=start]' ),
- endFillerContent = editable.findOne( this.fillerTagName + '[' + this.fillerAttribute + '=end]' );
-
- if ( this.startFiller && startFillerContent && this.startFiller.equals( startFillerContent ) ) {
- this.removeFiller( this.startFiller, editable );
- } else {
- // The start filler is still present but it is a different element than previous one. It means the
- // undo recreating entirely selected content was performed. We need to update filler reference.
- this.startFiller = startFillerContent;
- }
-
- if ( this.endFiller && endFillerContent && this.endFiller.equals( endFillerContent ) ) {
- this.removeFiller( this.endFiller, editable );
- } else {
- // Same as with start filler.
- this.endFiller = endFillerContent;
- }
- }
- },
-
- /**
- * Removes fillers from the paste data.
- *
- * @param {String} data
- * @returns {String}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- cleanPasteData: function( data ) {
- if ( data && data.length ) {
- data = data
- .replace( this.createFillerRegex(), '' )
- .replace( this.createFillerRegex( true ), '' );
- }
- return data;
- },
-
- /**
- * Checks if the entire content of the given editable is selected.
- *
- * @param {CKEDITOR.editable} editable
- * @returns {Boolean}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- isWholeContentSelected: function( editable ) {
-
- var range = editable.editor.getSelection().getRanges()[ 0 ];
- if ( range ) {
-
- if ( range && range.collapsed ) {
- return false;
-
- } else {
- var rangeClone = range.clone();
- rangeClone.enlarge( CKEDITOR.ENLARGE_ELEMENT );
-
- return !!( rangeClone && editable && rangeClone.startContainer && rangeClone.endContainer &&
- rangeClone.startOffset === 0 && rangeClone.endOffset === editable.getChildCount() &&
- rangeClone.startContainer.equals( editable ) && rangeClone.endContainer.equals( editable ) );
- }
- }
- return false;
- },
-
- /**
- * Checks if there is any filler element in the given editable.
- *
- * @param {CKEDITOR.editable} editable
- * @returns {Boolean}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- hasFiller: function( editable ) {
- return editable.find( this.fillerTagName + '[' + this.fillerAttribute + ']' ).count() > 0;
- },
-
- /**
- * Creates a filler element.
- *
- * @param {Boolean} [onEnd] If filler will be placed on end or beginning of the content.
- * @returns {CKEDITOR.dom.element}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- createFiller: function( onEnd ) {
- var filler = new CKEDITOR.dom.element( this.fillerTagName );
- filler.setHtml( this.fillerContent );
- filler.setAttribute( this.fillerAttribute, onEnd ? 'end' : 'start' );
- filler.setAttribute( 'data-cke-temp', 1 );
- filler.setStyles( {
- display: 'block',
- width: 0,
- height: 0,
- padding: 0,
- border: 0,
- margin: 0,
- position: 'absolute',
- top: 0,
- left: '-9999px',
- opacity: 0,
- overflow: 'hidden'
- } );
-
- return filler;
- },
-
- /**
- * Removes the specific filler element from the given editable. If the filler contains any content (typed or pasted),
- * it replaces the current editable content. If not, the caret is placed before the first or after the last editable
- * element (depends if the filler was at the beginning or the end).
- *
- * @param {CKEDITOR.dom.element} filler
- * @param {CKEDITOR.editable} editable
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- removeFiller: function( filler, editable ) {
- if ( filler ) {
- var editor = editable.editor,
- currentRange = editable.editor.getSelection().getRanges()[ 0 ],
- currentPath = currentRange.startPath(),
- range = editor.createRange(),
- insertedHtml,
- fillerOnStart,
- manuallyHandleCaret;
-
- if ( currentPath.contains( filler ) ) {
- insertedHtml = filler.getHtml();
- manuallyHandleCaret = true;
- }
-
- fillerOnStart = filler.getAttribute( this.fillerAttribute ) == 'start';
- filler.remove();
- filler = null;
-
- if ( insertedHtml && insertedHtml.length > 0 && insertedHtml != this.fillerContent ) {
- editable.insertHtmlIntoRange( insertedHtml, editor.getSelection().getRanges()[ 0 ] );
- range.setStartAt( editable.getChild( editable.getChildCount() - 1 ), CKEDITOR.POSITION_BEFORE_END );
- editor.getSelection().selectRanges( [ range ] );
-
- } else if ( manuallyHandleCaret ) {
- if ( fillerOnStart ) {
- range.setStartAt( editable.getFirst().getNext(), CKEDITOR.POSITION_AFTER_START );
- } else {
- range.setEndAt( editable.getLast().getPrevious(), CKEDITOR.POSITION_BEFORE_END );
- }
- editable.editor.getSelection().selectRanges( [ range ] );
- }
- }
- },
-
- /**
- * Creates a regular expression which will match the filler HTML in the text.
- *
- * @param {Boolean} [onEnd] Whether a regular expression should be created for the filler at the beginning or
- * the end of the content.
- * @returns {RegExp}
- * @member CKEDITOR.plugins.widgetselection
- * @private
- */
- createFillerRegex: function( onEnd ) {
- var matcher = this.createFiller( onEnd ).getOuterHtml()
- .replace( /style="[^"]*"/gi, 'style="[^"]*"' )
- .replace( />[^<]*</gi, '>[^<]*<' );
-
- return new RegExp( ( !onEnd ? '^' : '' ) + matcher + ( onEnd ? '$' : '' ) );
- },
-
- /**
- * Adds an integration for the [Select All](http://ckeditor.com/addon/selectall) plugin to the given `editor`.
- *
- * @private
- * @param {CKEDITOR.editor} editor
- * @member CKEDITOR.plugins.widgetselection
- */
- addSelectAllIntegration: function( editor ) {
- var widgetselection = this;
-
- editor.editable().attachListener( editor, 'beforeCommandExec', function( evt ) {
- var editable = editor.editable();
-
- if ( evt.data.name == 'selectAll' && editable ) {
- widgetselection.addFillers( editable );
- }
- }, null, null, 9999 );
- }
- };
-
-
- function filterTempElements( el ) {
- return el.getName && !el.hasAttribute( 'data-cke-temp' );
- }
-
- } )();
|