1/**
2 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6CKEDITOR.dialog.add( 'specialchar', function( editor ) {
7	// Simulate "this" of a dialog for non-dialog events.
8	// @type {CKEDITOR.dialog}
9	var dialog,
10		lang = editor.lang.specialchar;
11
12	var onChoice = function( evt ) {
13			var target, value;
14			if ( evt.data )
15				target = evt.data.getTarget();
16			else
17				target = new CKEDITOR.dom.element( evt );
18
19			if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) ) {
20				target.removeClass( 'cke_light_background' );
21				dialog.hide();
22
23				// We must use "insertText" here to keep text styled.
24				var span = editor.document.createElement( 'span' );
25				span.setHtml( value );
26				editor.insertText( span.getText() );
27			}
28		};
29
30	var onClick = CKEDITOR.tools.addFunction( onChoice );
31
32	var focusedNode;
33
34	var onFocus = function( evt, target ) {
35			var value;
36			target = target || evt.data.getTarget();
37
38			if ( target.getName() == 'span' )
39				target = target.getParent();
40
41			if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) ) {
42				// Trigger blur manually if there is focused node.
43				if ( focusedNode )
44					onBlur( null, focusedNode );
45
46				var htmlPreview = dialog.getContentElement( 'info', 'htmlPreview' ).getElement();
47
48				dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( value );
49				htmlPreview.setHtml( CKEDITOR.tools.htmlEncode( value ) );
50				target.getParent().addClass( 'cke_light_background' );
51
52				// Memorize focused node.
53				focusedNode = target;
54			}
55		};
56
57	var onBlur = function( evt, target ) {
58			target = target || evt.data.getTarget();
59
60			if ( target.getName() == 'span' )
61				target = target.getParent();
62
63			if ( target.getName() == 'a' ) {
64				dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( ' ' );
65				dialog.getContentElement( 'info', 'htmlPreview' ).getElement().setHtml( ' ' );
66				target.getParent().removeClass( 'cke_light_background' );
67
68				focusedNode = undefined;
69			}
70		};
71
72	var onKeydown = CKEDITOR.tools.addFunction( function( ev ) {
73		ev = new CKEDITOR.dom.event( ev );
74
75		// Get an Anchor element.
76		var element = ev.getTarget();
77		var relative, nodeToMove;
78		var keystroke = ev.getKeystroke(),
79			rtl = editor.lang.dir == 'rtl';
80
81		switch ( keystroke ) {
82			// UP-ARROW
83			case 38:
84				// relative is TR
85				if ( ( relative = element.getParent().getParent().getPrevious() ) ) {
86					nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] );
87					nodeToMove.focus();
88					onBlur( null, element );
89					onFocus( null, nodeToMove );
90				}
91				ev.preventDefault();
92				break;
93				// DOWN-ARROW
94			case 40:
95				// relative is TR
96				if ( ( relative = element.getParent().getParent().getNext() ) ) {
97					nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] );
98					if ( nodeToMove && nodeToMove.type == 1 ) {
99						nodeToMove.focus();
100						onBlur( null, element );
101						onFocus( null, nodeToMove );
102					}
103				}
104				ev.preventDefault();
105				break;
106				// SPACE
107				// ENTER is already handled as onClick
108			case 32:
109				onChoice( { data: ev } );
110				ev.preventDefault();
111				break;
112
113				// RIGHT-ARROW
114			case rtl ? 37 : 39:
115				// relative is TD
116				if ( ( relative = element.getParent().getNext() ) ) {
117					nodeToMove = relative.getChild( 0 );
118					if ( nodeToMove.type == 1 ) {
119						nodeToMove.focus();
120						onBlur( null, element );
121						onFocus( null, nodeToMove );
122						ev.preventDefault( true );
123					} else {
124						onBlur( null, element );
125					}
126				}
127				// relative is TR
128				else if ( ( relative = element.getParent().getParent().getNext() ) ) {
129					nodeToMove = relative.getChild( [ 0, 0 ] );
130					if ( nodeToMove && nodeToMove.type == 1 ) {
131						nodeToMove.focus();
132						onBlur( null, element );
133						onFocus( null, nodeToMove );
134						ev.preventDefault( true );
135					} else {
136						onBlur( null, element );
137					}
138				}
139				break;
140
141				// LEFT-ARROW
142			case rtl ? 39 : 37:
143				// relative is TD
144				if ( ( relative = element.getParent().getPrevious() ) ) {
145					nodeToMove = relative.getChild( 0 );
146					nodeToMove.focus();
147					onBlur( null, element );
148					onFocus( null, nodeToMove );
149					ev.preventDefault( true );
150				}
151				// relative is TR
152				else if ( ( relative = element.getParent().getParent().getPrevious() ) ) {
153					nodeToMove = relative.getLast().getChild( 0 );
154					nodeToMove.focus();
155					onBlur( null, element );
156					onFocus( null, nodeToMove );
157					ev.preventDefault( true );
158				} else {
159					onBlur( null, element );
160				}
161				break;
162			default:
163				// Do not stop not handled events.
164				return;
165		}
166	} );
167
168	return {
169		title: lang.title,
170		minWidth: 430,
171		minHeight: 280,
172		buttons: [ CKEDITOR.dialog.cancelButton ],
173		charColumns: 17,
174		onLoad: function() {
175			var columns = this.definition.charColumns,
176			chars = editor.config.specialChars;
177      		        extraChars = editor.config.extraSpecialChars;
178                        chars = chars.concat(extraChars);
179			var charsTableLabel = CKEDITOR.tools.getNextId() + '_specialchar_table_label';
180			var html = [ '<table role="listbox" aria-labelledby="' + charsTableLabel + '"' +
181				' style="width: 320px; height: 100%; border-collapse: separate;"' +
182				' align="center" cellspacing="2" cellpadding="2" border="0">' ];
183
184			var i = 0,
185				size = chars.length,
186				character, charDesc;
187
188			while ( i < size ) {
189				html.push( '<tr role="presentation">' );
190
191				for ( var j = 0; j < columns; j++, i++ ) {
192					if ( ( character = chars[ i ] ) ) {
193						charDesc = '';
194
195						if ( character instanceof Array ) {
196							charDesc = character[ 1 ];
197							character = character[ 0 ];
198						} else {
199							var _tmpName = character.replace( '&', '' ).replace( ';', '' ).replace( '#', '' );
200
201							// Use character in case description unavailable.
202							charDesc = lang[ _tmpName ] || character;
203						}
204
205						var charLabelId = 'cke_specialchar_label_' + i + '_' + CKEDITOR.tools.getNextNumber();
206
207						html.push( '<td class="cke_dark_background" style="cursor: default" role="presentation">' +
208							'<a href="javascript: void(0);" role="option"' +
209							' aria-posinset="' + ( i + 1 ) + '"', ' aria-setsize="' + size + '"', ' aria-labelledby="' + charLabelId + '"', ' class="cke_specialchar" title="', CKEDITOR.tools.htmlEncode( charDesc ), '"' +
210							' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydown + ', event, this )"' +
211							' onclick="CKEDITOR.tools.callFunction(' + onClick + ', this); return false;"' +
212							' tabindex="-1">' +
213							'<span style="margin: 0 auto;cursor: inherit">' +
214							character +
215							'</span>' +
216							'<span class="cke_voice_label" id="' + charLabelId + '">' +
217							charDesc +
218							'</span></a>' );
219					} else {
220						html.push( '<td class="cke_dark_background">&nbsp;' );
221					}
222
223					html.push( '</td>' );
224				}
225				html.push( '</tr>' );
226			}
227
228			html.push( '</tbody></table>', '<span id="' + charsTableLabel + '" class="cke_voice_label">' + lang.options + '</span>' );
229
230			this.getContentElement( 'info', 'charContainer' ).getElement().setHtml( html.join( '' ) );
231		},
232		contents: [ {
233			id: 'info',
234			label: editor.lang.common.generalTab,
235			title: editor.lang.common.generalTab,
236			padding: 0,
237			align: 'top',
238			elements: [ {
239				type: 'hbox',
240				align: 'top',
241				widths: [ '320px', '90px' ],
242				children: [ {
243					type: 'html',
244					id: 'charContainer',
245					html: '',
246					onMouseover: onFocus,
247					onMouseout: onBlur,
248					focus: function() {
249						var firstChar = this.getElement().getElementsByTag( 'a' ).getItem( 0 );
250						setTimeout( function() {
251							firstChar.focus();
252							onFocus( null, firstChar );
253						}, 0 );
254					},
255					onShow: function() {
256						var firstChar = this.getElement().getChild( [ 0, 0, 0, 0, 0 ] );
257						setTimeout( function() {
258							firstChar.focus();
259							onFocus( null, firstChar );
260						}, 0 );
261					},
262					onLoad: function( event ) {
263						dialog = event.sender;
264					}
265				},
266				{
267					type: 'hbox',
268					align: 'top',
269					widths: [ '100%' ],
270					children: [ {
271						type: 'vbox',
272						align: 'top',
273						children: [
274							{
275								type: 'html',
276								html: '<div></div>'
277							},
278							{
279								type: 'html',
280								id: 'charPreview',
281								className: 'cke_dark_background',
282								style: 'border:1px solid #eeeeee;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
283								html: '<div>&nbsp;</div>'
284							},
285							{
286								type: 'html',
287								id: 'htmlPreview',
288								className: 'cke_dark_background',
289								style: 'border:1px solid #eeeeee;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
290								html: '<div>&nbsp;</div>'
291							}
292						]
293					} ]
294				} ]
295			} ]
296		} ]
297	};
298} );
299