1 /*
  2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5 
  6 /**
  7  * @file Justify commands.
  8  */
  9 
 10 (function()
 11 {
 12 	function getAlignment( element, useComputedState )
 13 	{
 14 		useComputedState = useComputedState === undefined || useComputedState;
 15 
 16 		var align;
 17 		if ( useComputedState )
 18 			align = element.getComputedStyle( 'text-align' );
 19 		else
 20 		{
 21 			while ( !element.hasAttribute || !( element.hasAttribute( 'align' ) || element.getStyle( 'text-align' ) ) )
 22 			{
 23 				var parent = element.getParent();
 24 				if ( !parent )
 25 					break;
 26 				element = parent;
 27 			}
 28 			align = element.getStyle( 'text-align' ) || element.getAttribute( 'align' ) || '';
 29 		}
 30 
 31 		// Sometimes computed values doesn't tell.
 32 		align && ( align = align.replace( /(?:-(?:moz|webkit)-)?(?:start|auto)/i, '' ) );
 33 
 34 		!align && useComputedState && ( align = element.getComputedStyle( 'direction' ) == 'rtl' ? 'right' : 'left' );
 35 
 36 		return align;
 37 	}
 38 
 39 	function onSelectionChange( evt )
 40 	{
 41 		if ( evt.editor.readOnly )
 42 			return;
 43 
 44 		evt.editor.getCommand( this.name ).refresh( evt.data.path );
 45 	}
 46 
 47 	function justifyCommand( editor, name, value )
 48 	{
 49 		this.editor = editor;
 50 		this.name = name;
 51 		this.value = value;
 52 
 53 		var classes = editor.config.justifyClasses;
 54 		if ( classes )
 55 		{
 56 			switch ( value )
 57 			{
 58 				case 'left' :
 59 					this.cssClassName = classes[0];
 60 					break;
 61 				case 'center' :
 62 					this.cssClassName = classes[1];
 63 					break;
 64 				case 'right' :
 65 					this.cssClassName = classes[2];
 66 					break;
 67 				case 'justify' :
 68 					this.cssClassName = classes[3];
 69 					break;
 70 			}
 71 
 72 			this.cssClassRegex = new RegExp( '(?:^|\\s+)(?:' + classes.join( '|' ) + ')(?=$|\\s)' );
 73 		}
 74 	}
 75 
 76 	function onDirChanged( e )
 77 	{
 78 		var editor = e.editor;
 79 
 80 		var range = new CKEDITOR.dom.range( editor.document );
 81 		range.setStartBefore( e.data.node );
 82 		range.setEndAfter( e.data.node );
 83 
 84 		var walker = new CKEDITOR.dom.walker( range ),
 85 			node;
 86 
 87 		while ( ( node = walker.next() ) )
 88 		{
 89 			if ( node.type == CKEDITOR.NODE_ELEMENT )
 90 			{
 91 				// A child with the defined dir is to be ignored.
 92 				if ( !node.equals( e.data.node ) && node.getDirection() )
 93 				{
 94 					range.setStartAfter( node );
 95 					walker = new CKEDITOR.dom.walker( range );
 96 					continue;
 97 				}
 98 
 99 				// Switch the alignment.
100 				var classes = editor.config.justifyClasses;
101 				if ( classes )
102 				{
103 					// The left align class.
104 					if ( node.hasClass( classes[ 0 ] ) )
105 					{
106 						node.removeClass( classes[ 0 ] );
107 						node.addClass( classes[ 2 ] );
108 					}
109 					// The right align class.
110 					else if ( node.hasClass( classes[ 2 ] ) )
111 					{
112 						node.removeClass( classes[ 2 ] );
113 						node.addClass( classes[ 0 ] );
114 					}
115 				}
116 
117 				// Always switch CSS margins.
118 				var style = 'text-align';
119 				var align = node.getStyle( style );
120 
121 				if ( align == 'left' )
122 					node.setStyle( style, 'right' );
123 				else if ( align == 'right' )
124 					node.setStyle( style, 'left' );
125 			}
126 		}
127 	}
128 
129 	justifyCommand.prototype = {
130 		exec : function( editor )
131 		{
132 			var selection = editor.getSelection(),
133 				enterMode = editor.config.enterMode;
134 
135 			if ( !selection )
136 				return;
137 
138 			var bookmarks = selection.createBookmarks(),
139 				ranges = selection.getRanges( true );
140 
141 			var cssClassName = this.cssClassName,
142 				iterator,
143 				block;
144 
145 			var useComputedState = editor.config.useComputedState;
146 			useComputedState = useComputedState === undefined || useComputedState;
147 
148 			for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
149 			{
150 				iterator = ranges[ i ].createIterator();
151 				iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
152 
153 				while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) )
154 				{
155 					block.removeAttribute( 'align' );
156 					block.removeStyle( 'text-align' );
157 
158 					// Remove any of the alignment classes from the className.
159 					var className = cssClassName && ( block.$.className =
160 						CKEDITOR.tools.ltrim( block.$.className.replace( this.cssClassRegex, '' ) ) );
161 
162 					var apply =
163 						( this.state == CKEDITOR.TRISTATE_OFF ) &&
164 						( !useComputedState || ( getAlignment( block, true ) != this.value ) );
165 
166 					if ( cssClassName )
167 					{
168 						// Append the desired class name.
169 						if ( apply )
170 							block.addClass( cssClassName );
171 						else if ( !className )
172 							block.removeAttribute( 'class' );
173 					}
174 					else if ( apply )
175 						block.setStyle( 'text-align', this.value );
176 				}
177 
178 			}
179 
180 			editor.focus();
181 			editor.forceNextSelectionCheck();
182 			selection.selectBookmarks( bookmarks );
183 		},
184 
185 		refresh : function( path )
186 		{
187 			var firstBlock = path.block || path.blockLimit;
188 
189 			this.setState( firstBlock.getName() != 'body' &&
190 				getAlignment( firstBlock, this.editor.config.useComputedState ) == this.value ?
191 				CKEDITOR.TRISTATE_ON :
192 				CKEDITOR.TRISTATE_OFF );
193 		}
194 	};
195 
196 	CKEDITOR.plugins.add( 'justify',
197 	{
198 		init : function( editor )
199 		{
200 			var left = new justifyCommand( editor, 'justifyleft', 'left' ),
201 				center = new justifyCommand( editor, 'justifycenter', 'center' ),
202 				right = new justifyCommand( editor, 'justifyright', 'right' ),
203 				justify = new justifyCommand( editor, 'justifyblock', 'justify' );
204 
205 			editor.addCommand( 'justifyleft', left );
206 			editor.addCommand( 'justifycenter', center );
207 			editor.addCommand( 'justifyright', right );
208 			editor.addCommand( 'justifyblock', justify );
209 
210 			editor.ui.addButton( 'JustifyLeft',
211 				{
212 					label : editor.lang.justify.left,
213 					command : 'justifyleft'
214 				} );
215 			editor.ui.addButton( 'JustifyCenter',
216 				{
217 					label : editor.lang.justify.center,
218 					command : 'justifycenter'
219 				} );
220 			editor.ui.addButton( 'JustifyRight',
221 				{
222 					label : editor.lang.justify.right,
223 					command : 'justifyright'
224 				} );
225 			editor.ui.addButton( 'JustifyBlock',
226 				{
227 					label : editor.lang.justify.block,
228 					command : 'justifyblock'
229 				} );
230 
231 			editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, left ) );
232 			editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, right ) );
233 			editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, center ) );
234 			editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, justify ) );
235 			editor.on( 'dirChanged', onDirChanged );
236 		},
237 
238 		requires : [ 'domiterator' ]
239 	});
240 })();
241 
242  /**
243  * List of classes to use for aligning the contents. If it's null, no classes will be used
244  * and instead the corresponding CSS values will be used. The array should contain 4 members, in the following order: left, center, right, justify.
245  * @name CKEDITOR.config.justifyClasses
246  * @type Array
247  * @default null
248  * @example
249  * // Use the classes 'AlignLeft', 'AlignCenter', 'AlignRight', 'AlignJustify'
250  * config.justifyClasses = [ 'AlignLeft', 'AlignCenter', 'AlignRight', 'AlignJustify' ];
251  */
252