123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- /**
- * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or http://ckeditor.com/license
- */
- /**
- * @fileOverview Increase and Decrease Indent commands.
- */
- ( function() {
- 'use strict';
- var TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED,
- TRISTATE_OFF = CKEDITOR.TRISTATE_OFF;
- CKEDITOR.plugins.add( 'indent', {
- // jscs:disable maximumLineLength
- lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
- // jscs:enable maximumLineLength
- icons: 'indent,indent-rtl,outdent,outdent-rtl', // %REMOVE_LINE_CORE%
- hidpi: true, // %REMOVE_LINE_CORE%
- init: function( editor ) {
- var genericDefinition = CKEDITOR.plugins.indent.genericDefinition;
- // Register generic commands.
- setupGenericListeners( editor, editor.addCommand( 'indent', new genericDefinition( true ) ) );
- setupGenericListeners( editor, editor.addCommand( 'outdent', new genericDefinition() ) );
- // Create and register toolbar button if possible.
- if ( editor.ui.addButton ) {
- editor.ui.addButton( 'Indent', {
- label: editor.lang.indent.indent,
- command: 'indent',
- directional: true,
- toolbar: 'indent,20'
- } );
- editor.ui.addButton( 'Outdent', {
- label: editor.lang.indent.outdent,
- command: 'outdent',
- directional: true,
- toolbar: 'indent,10'
- } );
- }
- // Register dirChanged listener.
- editor.on( 'dirChanged', function( evt ) {
- var range = editor.createRange(),
- dataNode = evt.data.node;
- range.setStartBefore( dataNode );
- range.setEndAfter( dataNode );
- var walker = new CKEDITOR.dom.walker( range ),
- node;
- while ( ( node = walker.next() ) ) {
- if ( node.type == CKEDITOR.NODE_ELEMENT ) {
- // A child with the defined dir is to be ignored.
- if ( !node.equals( dataNode ) && node.getDirection() ) {
- range.setStartAfter( node );
- walker = new CKEDITOR.dom.walker( range );
- continue;
- }
- // Switch alignment classes.
- var classes = editor.config.indentClasses;
- if ( classes ) {
- var suffix = ( evt.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];
- for ( var i = 0; i < classes.length; i++ ) {
- if ( node.hasClass( classes[ i ] + suffix[ 0 ] ) ) {
- node.removeClass( classes[ i ] + suffix[ 0 ] );
- node.addClass( classes[ i ] + suffix[ 1 ] );
- }
- }
- }
- // Switch the margins.
- var marginLeft = node.getStyle( 'margin-right' ),
- marginRight = node.getStyle( 'margin-left' );
- marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );
- marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );
- }
- }
- } );
- }
- } );
- /**
- * Global command class definitions and global helpers.
- *
- * @class
- * @singleton
- */
- CKEDITOR.plugins.indent = {
- /**
- * A base class for a generic command definition, responsible mainly for creating
- * Increase Indent and Decrease Indent toolbar buttons as well as for refreshing
- * UI states.
- *
- * Commands of this class do not perform any indentation by themselves. They
- * delegate this job to content-specific indentation commands (i.e. indentlist).
- *
- * @class CKEDITOR.plugins.indent.genericDefinition
- * @extends CKEDITOR.commandDefinition
- * @param {CKEDITOR.editor} editor The editor instance this command will be
- * applied to.
- * @param {String} name The name of the command.
- * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
- */
- genericDefinition: function( isIndent ) {
- /**
- * Determines whether the command belongs to the indentation family.
- * Otherwise it is assumed to be an outdenting command.
- *
- * @readonly
- * @property {Boolean} [=false]
- */
- this.isIndent = !!isIndent;
- // Mimic naive startDisabled behavior for outdent.
- this.startDisabled = !this.isIndent;
- },
- /**
- * A base class for specific indentation command definitions responsible for
- * handling a pre-defined set of elements i.e. indentlist for lists or
- * indentblock for text block elements.
- *
- * Commands of this class perform indentation operations and modify the DOM structure.
- * They listen for events fired by {@link CKEDITOR.plugins.indent.genericDefinition}
- * and execute defined actions.
- *
- * **NOTE**: This is not an {@link CKEDITOR.command editor command}.
- * Context-specific commands are internal, for indentation system only.
- *
- * @class CKEDITOR.plugins.indent.specificDefinition
- * @param {CKEDITOR.editor} editor The editor instance this command will be
- * applied to.
- * @param {String} name The name of the command.
- * @param {Boolean} [isIndent] Defines the command as indenting or outdenting.
- */
- specificDefinition: function( editor, name, isIndent ) {
- this.name = name;
- this.editor = editor;
- /**
- * An object of jobs handled by the command. Each job consists
- * of two functions: `refresh` and `exec` as well as the execution priority.
- *
- * * The `refresh` function determines whether a job is doable for
- * a particular context. These functions are executed in the
- * order of priorities, one by one, for all plugins that registered
- * jobs. As jobs are related to generic commands, refreshing
- * occurs when the global command is firing the `refresh` event.
- *
- * **Note**: This function must return either {@link CKEDITOR#TRISTATE_DISABLED}
- * or {@link CKEDITOR#TRISTATE_OFF}.
- *
- * * The `exec` function modifies the DOM if possible. Just like
- * `refresh`, `exec` functions are executed in the order of priorities
- * while the generic command is executed. This function is not executed
- * if `refresh` for this job returned {@link CKEDITOR#TRISTATE_DISABLED}.
- *
- * **Note**: This function must return a Boolean value, indicating whether it
- * was successful. If a job was successful, then no other jobs are being executed.
- *
- * Sample definition:
- *
- * command.jobs = {
- * // Priority = 20.
- * '20': {
- * refresh( editor, path ) {
- * if ( condition )
- * return CKEDITOR.TRISTATE_OFF;
- * else
- * return CKEDITOR.TRISTATE_DISABLED;
- * },
- * exec( editor ) {
- * // DOM modified! This was OK.
- * return true;
- * }
- * },
- * // Priority = 60. This job is done later.
- * '60': {
- * // Another job.
- * }
- * };
- *
- * For additional information, please check comments for
- * the `setupGenericListeners` function.
- *
- * @readonly
- * @property {Object} [={}]
- */
- this.jobs = {};
- /**
- * Determines whether the editor that the command belongs to has
- * {@link CKEDITOR.config#enterMode config.enterMode} set to {@link CKEDITOR#ENTER_BR}.
- *
- * @readonly
- * @see CKEDITOR.config#enterMode
- * @property {Boolean} [=false]
- */
- this.enterBr = editor.config.enterMode == CKEDITOR.ENTER_BR;
- /**
- * Determines whether the command belongs to the indentation family.
- * Otherwise it is assumed to be an outdenting command.
- *
- * @readonly
- * @property {Boolean} [=false]
- */
- this.isIndent = !!isIndent;
- /**
- * The name of the global command related to this one.
- *
- * @readonly
- */
- this.relatedGlobal = isIndent ? 'indent' : 'outdent';
- /**
- * A keystroke associated with this command (*Tab* or *Shift+Tab*).
- *
- * @readonly
- */
- this.indentKey = isIndent ? 9 : CKEDITOR.SHIFT + 9;
- /**
- * Stores created markers for the command so they can eventually be
- * purged after the `exec` function is run.
- */
- this.database = {};
- },
- /**
- * Registers content-specific commands as a part of the indentation system
- * directed by generic commands. Once a command is registered,
- * it listens for events of a related generic command.
- *
- * CKEDITOR.plugins.indent.registerCommands( editor, {
- * 'indentlist': new indentListCommand( editor, 'indentlist' ),
- * 'outdentlist': new indentListCommand( editor, 'outdentlist' )
- * } );
- *
- * Content-specific commands listen for the generic command's `exec` and
- * try to execute their own jobs, one after another. If some execution is
- * successful, `evt.data.done` is set so no more jobs (commands) are involved.
- *
- * Content-specific commands also listen for the generic command's `refresh`
- * and fill the `evt.data.states` object with states of jobs. A generic command
- * uses this data to determine its own state and to update the UI.
- *
- * @member CKEDITOR.plugins.indent
- * @param {CKEDITOR.editor} editor The editor instance this command is
- * applied to.
- * @param {Object} commands An object of {@link CKEDITOR.command}.
- */
- registerCommands: function( editor, commands ) {
- editor.on( 'pluginsLoaded', function() {
- for ( var name in commands ) {
- ( function( editor, command ) {
- var relatedGlobal = editor.getCommand( command.relatedGlobal );
- for ( var priority in command.jobs ) {
- // Observe generic exec event and execute command when necessary.
- // If the command was successfully handled by the command and
- // DOM has been modified, stop event propagation so no other plugin
- // will bother. Job is done.
- relatedGlobal.on( 'exec', function( evt ) {
- if ( evt.data.done )
- return;
- // Make sure that anything this command will do is invisible
- // for undoManager. What undoManager only can see and
- // remember is the execution of the global command (relatedGlobal).
- editor.fire( 'lockSnapshot' );
- if ( command.execJob( editor, priority ) )
- evt.data.done = true;
- editor.fire( 'unlockSnapshot' );
- // Clean up the markers.
- CKEDITOR.dom.element.clearAllMarkers( command.database );
- }, this, null, priority );
- // Observe generic refresh event and force command refresh.
- // Once refreshed, save command state in event data
- // so generic command plugin can update its own state and UI.
- relatedGlobal.on( 'refresh', function( evt ) {
- if ( !evt.data.states )
- evt.data.states = {};
- evt.data.states[ command.name + '@' + priority ] =
- command.refreshJob( editor, priority, evt.data.path );
- }, this, null, priority );
- }
- // Since specific indent commands have no UI elements,
- // they need to be manually registered as a editor feature.
- editor.addFeature( command );
- } )( this, commands[ name ] );
- }
- } );
- }
- };
- CKEDITOR.plugins.indent.genericDefinition.prototype = {
- context: 'p',
- exec: function() {}
- };
- CKEDITOR.plugins.indent.specificDefinition.prototype = {
- /**
- * Executes the content-specific procedure if the context is correct.
- * It calls the `exec` function of a job of the given `priority`
- * that modifies the DOM.
- *
- * @param {CKEDITOR.editor} editor The editor instance this command
- * will be applied to.
- * @param {Number} priority The priority of the job to be executed.
- * @returns {Boolean} Indicates whether the job was successful.
- */
- execJob: function( editor, priority ) {
- var job = this.jobs[ priority ];
- if ( job.state != TRISTATE_DISABLED )
- return job.exec.call( this, editor );
- },
- /**
- * Calls the `refresh` function of a job of the given `priority`.
- * The function returns the state of the job which can be either
- * {@link CKEDITOR#TRISTATE_DISABLED} or {@link CKEDITOR#TRISTATE_OFF}.
- *
- * @param {CKEDITOR.editor} editor The editor instance this command
- * will be applied to.
- * @param {Number} priority The priority of the job to be executed.
- * @returns {Number} The state of the job.
- */
- refreshJob: function( editor, priority, path ) {
- var job = this.jobs[ priority ];
- if ( !editor.activeFilter.checkFeature( this ) )
- job.state = TRISTATE_DISABLED;
- else
- job.state = job.refresh.call( this, editor, path );
- return job.state;
- },
- /**
- * Checks if the element path contains the element handled
- * by this indentation command.
- *
- * @param {CKEDITOR.dom.elementPath} node A path to be checked.
- * @returns {CKEDITOR.dom.element}
- */
- getContext: function( path ) {
- return path.contains( this.context );
- }
- };
- /**
- * Attaches event listeners for this generic command. Since the indentation
- * system is event-oriented, generic commands communicate with
- * content-specific commands using the `exec` and `refresh` events.
- *
- * Listener priorities are crucial. Different indentation phases
- * are executed with different priorities.
- *
- * For the `exec` event:
- *
- * * 0: Selection and bookmarks are saved by the generic command.
- * * 1-99: Content-specific commands try to indent the code by executing
- * their own jobs ({@link CKEDITOR.plugins.indent.specificDefinition#jobs}).
- * * 100: Bookmarks are re-selected by the generic command.
- *
- * The visual interpretation looks as follows:
- *
- * +------------------+
- * | Exec event fired |
- * +------ + ---------+
- * |
- * 0 -<----------+ Selection and bookmarks saved.
- * |
- * |
- * 25 -<---+ Exec 1st job of plugin#1 (return false, continuing...).
- * |
- * |
- * 50 -<---+ Exec 1st job of plugin#2 (return false, continuing...).
- * |
- * |
- * 75 -<---+ Exec 2nd job of plugin#1 (only if plugin#2 failed).
- * |
- * |
- * 100 -<-----------+ Re-select bookmarks, clean-up.
- * |
- * +-------- v ----------+
- * | Exec event finished |
- * +---------------------+
- *
- * For the `refresh` event:
- *
- * * <100: Content-specific commands refresh their job states according
- * to the given path. Jobs save their states in the `evt.data.states` object
- * passed along with the event. This can be either {@link CKEDITOR#TRISTATE_DISABLED}
- * or {@link CKEDITOR#TRISTATE_OFF}.
- * * 100: Command state is determined according to what states
- * have been returned by content-specific jobs (`evt.data.states`).
- * UI elements are updated at this stage.
- *
- * **Note**: If there is at least one job with the {@link CKEDITOR#TRISTATE_OFF} state,
- * then the generic command state is also {@link CKEDITOR#TRISTATE_OFF}. Otherwise,
- * the command state is {@link CKEDITOR#TRISTATE_DISABLED}.
- *
- * @param {CKEDITOR.command} command The command to be set up.
- * @private
- */
- function setupGenericListeners( editor, command ) {
- var selection, bookmarks;
- // Set the command state according to content-specific
- // command states.
- command.on( 'refresh', function( evt ) {
- // If no state comes with event data, disable command.
- var states = [ TRISTATE_DISABLED ];
- for ( var s in evt.data.states )
- states.push( evt.data.states[ s ] );
- this.setState( CKEDITOR.tools.search( states, TRISTATE_OFF ) ? TRISTATE_OFF : TRISTATE_DISABLED );
- }, command, null, 100 );
- // Initialization. Save bookmarks and mark event as not handled
- // by any plugin (command) yet.
- command.on( 'exec', function( evt ) {
- selection = editor.getSelection();
- bookmarks = selection.createBookmarks( 1 );
- // Mark execution as not handled yet.
- if ( !evt.data )
- evt.data = {};
- evt.data.done = false;
- }, command, null, 0 );
- // Housekeeping. Make sure selectionChange will be called.
- // Also re-select previously saved bookmarks.
- command.on( 'exec', function() {
- editor.forceNextSelectionCheck();
- selection.selectBookmarks( bookmarks );
- }, command, null, 100 );
- }
- } )();
|