1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 CKEDITOR.dialog.add( 'link', function( editor )
  7 {
  8 	// Handles the event when the "Target" selection box is changed.
  9 	var targetChanged = function()
 10 	{
 11 		var dialog = this.getDialog(),
 12 			popupFeatures = dialog.getContentElement( 'target', 'popupFeatures' ),
 13 			targetName = dialog.getContentElement( 'target', 'linkTargetName' ),
 14 			value = this.getValue();
 15
 16 		if ( !popupFeatures || !targetName )
 17 			return;
 18
 19 		popupFeatures = popupFeatures.getElement();
 20
 21 		if ( value == 'popup' )
 22 		{
 23 			popupFeatures.show();
 24 			targetName.setLabel( editor.lang.link.targetPopupName );
 25 		}
 26 		else
 27 		{
 28 			popupFeatures.hide();
 29 			targetName.setLabel( editor.lang.link.targetFrameName );
 30 			this.getDialog().setValueOf( 'target', 'linkTargetName', value.charAt( 0 ) == '_' ? value : '' );
 31 		}
 32 	};
 33
 34 	// Handles the event when the "Type" selection box is changed.
 35 	var linkTypeChanged = function()
 36 	{
 37 		var dialog = this.getDialog(),
 38 			partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ],
 39 			typeValue = this.getValue(),
 40 			uploadInitiallyHidden = dialog.definition.getContents( 'upload' ).hidden;
 41
 42 		if ( typeValue == 'url' )
 43 		{
 44 			if ( editor.config.linkShowTargetTab )
 45 				dialog.showPage( 'target' );
 46 			if ( !uploadInitiallyHidden )
 47 				dialog.showPage( 'upload' );
 48 		}
 49 		else
 50 		{
 51 			dialog.hidePage( 'target' );
 52 			if ( !uploadInitiallyHidden )
 53 				dialog.hidePage( 'upload' );
 54 		}
 55
 56 		for ( var i = 0 ; i < partIds.length ; i++ )
 57 		{
 58 			var element = dialog.getContentElement( 'info', partIds[i] );
 59 			if ( !element )
 60 				continue;
 61
 62 			element = element.getElement().getParent().getParent();
 63 			if ( partIds[i] == typeValue + 'Options' )
 64 				element.show();
 65 			else
 66 				element.hide();
 67 		}
 68 	};
 69
 70 	// Loads the parameters in a selected link to the link dialog fields.
 71 	var emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/,
 72 		emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/,
 73 		emailBodyRegex = /body=([^;?:@&=$,\/]*)/,
 74 		anchorRegex = /^#(.*)$/,
 75 		urlRegex = /^((?:http|https|ftp|news):\/\/)?(.*)$/,
 76 		selectableTargets = /^(_(?:self|top|parent|blank))$/;
 77
 78 	var popupRegex =
 79 		/\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/;
 80 	var popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi;
 81
 82 	var parseLink = function( editor, element )
 83 	{
 84 		var href = element ? ( element.getAttribute( '_cke_saved_href' ) || element.getAttribute( 'href' ) ) : '',
 85 			emailMatch = '',
 86 			anchorMatch = '',
 87 			urlMatch = false,
 88 			retval = {};
 89
 90 		if ( href )
 91 		{
 92 			emailMatch = href.match( emailRegex );
 93 			anchorMatch = href.match( anchorRegex );
 94 			urlMatch = href.match( urlRegex );
 95 		}
 96
 97 		// Load the link type and URL.
 98 		if ( emailMatch )
 99 		{
100 			var subjectMatch = href.match( emailSubjectRegex ),
101 				bodyMatch = href.match( emailBodyRegex );
102 			retval.type = 'email';
103 			retval.email = {};
104 			retval.email.address = emailMatch[1];
105 			subjectMatch && ( retval.email.subject = decodeURIComponent( subjectMatch[1] ) );
106 			bodyMatch && ( retval.email.body = decodeURIComponent( bodyMatch[1] ) );
107 		}
108 		else if ( anchorMatch )
109 		{
110 			retval.type = 'anchor';
111 			retval.anchor = {};
112 			retval.anchor.name = retval.anchor.id = anchorMatch[1];
113 		}
114 		else if ( href && urlMatch )		// urlRegex matches empty strings, so need to check for href as well.
115 		{
116 			retval.type = 'url';
117 			retval.url = {};
118 			retval.url.protocol = urlMatch[1];
119 			retval.url.url = urlMatch[2];
120 		}
121 		else
122 			retval.type = 'url';
123
124 		// Load target and popup settings.
125 		if ( element )
126 		{
127 			var target = element.getAttribute( 'target' );
128 			retval.target = {};
129 			retval.adv = {};
130
131 			// IE BUG: target attribute is an empty string instead of null in IE if it's not set.
132 			if ( !target )
133 			{
134 				var onclick = element.getAttribute( '_cke_pa_onclick' ) || element.getAttribute( 'onclick' ),
135 					onclickMatch = onclick && onclick.match( popupRegex );
136 				if ( onclickMatch )
137 				{
138 					retval.target.type = 'popup';
139 					retval.target.name = onclickMatch[1];
140
141 					var featureMatch;
142 					while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[2] ) ) )
143 					{
144 						if ( featureMatch[2] == 'yes' || featureMatch[2] == '1' )
145 							retval.target[ featureMatch[1] ] = true;
146 						else if ( isFinite( featureMatch[2] ) )
147 							retval.target[ featureMatch[1] ] = featureMatch[2];
148 					}
149 				}
150 			}
151 			else
152 			{
153 				var targetMatch = target.match( selectableTargets );
154 				if ( targetMatch )
155 					retval.target.type = retval.target.name = target;
156 				else
157 				{
158 					retval.target.type = 'frame';
159 					retval.target.name = target;
160 				}
161 			}
162
163 			var me = this;
164 			var advAttr = function( inputName, attrName )
165 			{
166 				var value = element.getAttribute( attrName );
167 				if ( value !== null )
168 					retval.adv[ inputName ] = value || '';
169 			};
170 			advAttr( 'advId', 'id' );
171 			advAttr( 'advLangDir', 'dir' );
172 			advAttr( 'advAccessKey', 'accessKey' );
173 			advAttr( 'advName', 'name' );
174 			advAttr( 'advLangCode', 'lang' );
175 			advAttr( 'advTabIndex', 'tabindex' );
176 			advAttr( 'advTitle', 'title' );
177 			advAttr( 'advContentType', 'type' );
178 			advAttr( 'advCSSClasses', 'class' );
179 			advAttr( 'advCharset', 'charset' );
180 			advAttr( 'advStyles', 'style' );
181 		}
182
183 		// Find out whether we have any anchors in the editor.
184 		// Get all IMG elements in CK document.
185 		var elements = editor.document.getElementsByTag( 'img' ),
186 			realAnchors = new CKEDITOR.dom.nodeList( editor.document.$.anchors ),
187 			anchors = retval.anchors = [];
188
189 		for( var i = 0; i < elements.count() ; i++ )
190 		{
191 			var item = elements.getItem( i );
192 			if ( item.getAttribute( '_cke_realelement' ) && item.getAttribute( '_cke_real_element_type' ) == 'anchor' )
193 			{
194 				anchors.push( editor.restoreRealElement( item ) );
195 			}
196 		}
197
198 		for ( i = 0 ; i < realAnchors.count() ; i++ )
199 			anchors.push( realAnchors.getItem( i ) );
200
201 		for ( i = 0 ; i < anchors.length ; i++ )
202 		{
203 			item = anchors[ i ];
204 			anchors[ i ] = { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) };
205 		}
206
207 		// Record down the selected element in the dialog.
208 		this._.selectedElement = element;
209
210 		return retval;
211 	};
212
213 	var setupParams = function( page, data )
214 	{
215 		if ( data[page] )
216 			this.setValue( data[page][this.id] || '' );
217 	};
218
219 	var setupPopupParams = function( data )
220 	{
221 		return setupParams.call( this, 'target', data );
222 	};
223
224 	var setupAdvParams = function( data )
225 	{
226 		return setupParams.call( this, 'adv', data );
227 	};
228
229 	var commitParams = function( page, data )
230 	{
231 		if ( !data[page] )
232 			data[page] = {};
233
234 		data[page][this.id] = this.getValue() || '';
235 	};
236
237 	var commitPopupParams = function( data )
238 	{
239 		return commitParams.call( this, 'target', data );
240 	};
241
242 	var commitAdvParams = function( data )
243 	{
244 		return commitParams.call( this, 'adv', data );
245 	};
246
247 	return {
248 		title : editor.lang.link.title,
249 		minWidth : 350,
250 		minHeight : 230,
251 		contents : [
252 			{
253 				id : 'info',
254 				label : editor.lang.link.info,
255 				title : editor.lang.link.info,
256 				elements :
257 				[
258 					{
259 						id : 'linkType',
260 						type : 'select',
261 						label : editor.lang.link.type,
262 						'default' : 'url',
263 						items :
264 						[
265 							[ editor.lang.common.url, 'url' ],
266 							[ editor.lang.link.toAnchor, 'anchor' ],
267 							[ editor.lang.link.toEmail, 'email' ]
268 						],
269 						onChange : linkTypeChanged,
270 						setup : function( data )
271 						{
272 							if ( data.type )
273 								this.setValue( data.type );
274 						},
275 						commit : function( data )
276 						{
277 							data.type = this.getValue();
278 						}
279 					},
280 					{
281 						type : 'vbox',
282 						id : 'urlOptions',
283 						children :
284 						[
285 							{
286 								type : 'hbox',
287 								widths : [ '25%', '75%' ],
288 								children :
289 								[
290 									{
291 										id : 'protocol',
292 										type : 'select',
293 										label : editor.lang.common.protocol,
294 										'default' : 'http://',
295 										style : 'width : 100%;',
296 										items :
297 										[
298 											[ 'http://' ],
299 											[ 'https://' ],
300 											[ 'ftp://' ],
301 											[ 'news://' ],
302 											[ '<other>', '' ]
303 										],
304 										setup : function( data )
305 										{
306 											if ( data.url )
307 												this.setValue( data.url.protocol );
308 										},
309 										commit : function( data )
310 										{
311 											if ( !data.url )
312 												data.url = {};
313
314 											data.url.protocol = this.getValue();
315 										}
316 									},
317 									{
318 										type : 'text',
319 										id : 'url',
320 										label : editor.lang.common.url,
321 										onLoad : function ()
322 										{
323 											this.allowOnChange = true;
324 										},
325 										onKeyUp : function()
326 										{
327 											this.allowOnChange = false;
328 											var	protocolCmb = this.getDialog().getContentElement( 'info', 'protocol' ),
329 												url = this.getValue(),
330 												urlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/gi,
331 												urlOnChangeTestOther = /^((javascript:)|[#\/\.])/gi;
332
333 											var protocol = urlOnChangeProtocol.exec( url );
334 											if ( protocol )
335 											{
336 												this.setValue( url.substr( protocol[ 0 ].length ) );
337 												protocolCmb.setValue( protocol[ 0 ].toLowerCase() );
338 											}
339 											else if ( urlOnChangeTestOther.test( url ) )
340 												protocolCmb.setValue( '' );
341
342 											this.allowOnChange = true;
343 										},
344 										onChange : function()
345 										{
346 											if ( this.allowOnChange )		// Dont't call on dialog load.
347 												this.onKeyUp();
348 										},
349 										validate : function()
350 										{
351 											var dialog = this.getDialog();
352
353 											if ( dialog.getContentElement( 'info', 'linkType' ) &&
354 													dialog.getValueOf( 'info', 'linkType' ) != 'url' )
355 												return true;
356
357 											if ( this.getDialog().fakeObj )	// Edit Anchor.
358 												return true;
359
360 											var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noUrl );
361 											return func.apply( this );
362 										},
363 										setup : function( data )
364 										{
365 											this.allowOnChange = false;
366 											if ( data.url )
367 												this.setValue( data.url.url );
368 											this.allowOnChange = true;
369
370 											var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
371 											if ( linkType && linkType.getValue() == 'url' )
372 												this.select();
373
374 										},
375 										commit : function( data )
376 										{
377 											if ( !data.url )
378 												data.url = {};
379
380 											data.url.url = this.getValue();
381 											this.allowOnChange = false;
382 										}
383 									}
384 								],
385 								setup : function( data )
386 								{
387 									if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
388 										this.getElement().show();
389 								}
390 							},
391 							{
392 								type : 'button',
393 								id : 'browse',
394 								hidden : 'true',
395 								filebrowser : 'info:url',
396 								label : editor.lang.common.browseServer
397 							}
398 						]
399 					},
400 					{
401 						type : 'vbox',
402 						id : 'anchorOptions',
403 						width : 260,
404 						align : 'center',
405 						padding : 0,
406 						children :
407 						[
408 							{
409 								type : 'html',
410 								id : 'selectAnchorText',
411 								html : CKEDITOR.tools.htmlEncode( editor.lang.link.selectAnchor ),
412 								setup : function( data )
413 								{
414 									if ( data.anchors.length > 0 )
415 										this.getElement().show();
416 									else
417 										this.getElement().hide();
418 								}
419 							},
420 							{
421 								type : 'html',
422 								id : 'noAnchors',
423 								style : 'text-align: center;',
424 								html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.link.noAnchors ) + '</div>',
425 								setup : function( data )
426 								{
427 									if ( data.anchors.length < 1 )
428 										this.getElement().show();
429 									else
430 										this.getElement().hide();
431 								}
432 							},
433 							{
434 								type : 'hbox',
435 								id : 'selectAnchor',
436 								children :
437 								[
438 									{
439 										type : 'select',
440 										id : 'anchorName',
441 										'default' : '',
442 										label : editor.lang.link.anchorName,
443 										style : 'width: 100%;',
444 										items :
445 										[
446 											[ '' ]
447 										],
448 										setup : function( data )
449 										{
450 											this.clear();
451 											this.add( '' );
452 											for ( var i = 0 ; i < data.anchors.length ; i++ )
453 											{
454 												if ( data.anchors[i].name )
455 													this.add( data.anchors[i].name );
456 											}
457
458 											if ( data.anchor )
459 												this.setValue( data.anchor.name );
460
461 											var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
462 											if ( linkType && linkType.getValue() == 'email' )
463 												this.focus();
464 										},
465 										commit : function( data )
466 										{
467 											if ( !data.anchor )
468 												data.anchor = {};
469
470 											data.anchor.name = this.getValue();
471 										}
472 									},
473 									{
474 										type : 'select',
475 										id : 'anchorId',
476 										'default' : '',
477 										label : editor.lang.link.anchorId,
478 										style : 'width: 100%;',
479 										items :
480 										[
481 											[ '' ]
482 										],
483 										setup : function( data )
484 										{
485 											this.clear();
486 											this.add( '' );
487 											for ( var i = 0 ; i < data.anchors.length ; i++ )
488 											{
489 												if ( data.anchors[i].id )
490 													this.add( data.anchors[i].id );
491 											}
492
493 											if ( data.anchor )
494 												this.setValue( data.anchor.id );
495 										},
496 										commit : function( data )
497 										{
498 											if ( !data.anchor )
499 												data.anchor = {};
500
501 											data.anchor.id = this.getValue();
502 										}
503 									}
504 								],
505 								setup : function( data )
506 								{
507 									if ( data.anchors.length > 0 )
508 										this.getElement().show();
509 									else
510 										this.getElement().hide();
511 								}
512 							}
513 						],
514 						setup : function( data )
515 						{
516 							if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
517 								this.getElement().hide();
518 						}
519 					},
520 					{
521 						type :  'vbox',
522 						id : 'emailOptions',
523 						padding : 1,
524 						children :
525 						[
526 							{
527 								type : 'text',
528 								id : 'emailAddress',
529 								label : editor.lang.link.emailAddress,
530 								validate : function()
531 								{
532 									var dialog = this.getDialog();
533
534 									if ( !dialog.getContentElement( 'info', 'linkType' ) ||
535 											dialog.getValueOf( 'info', 'linkType' ) != 'email' )
536 										return true;
537
538 									var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noEmail );
539 									return func.apply( this );
540 								},
541 								setup : function( data )
542 								{
543 									if ( data.email )
544 										this.setValue( data.email.address );
545
546 									var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
547 									if ( linkType && linkType.getValue() == 'email' )
548 										this.select();
549 								},
550 								commit : function( data )
551 								{
552 									if ( !data.email )
553 										data.email = {};
554
555 									data.email.address = this.getValue();
556 								}
557 							},
558 							{
559 								type : 'text',
560 								id : 'emailSubject',
561 								label : editor.lang.link.emailSubject,
562 								setup : function( data )
563 								{
564 									if ( data.email )
565 										this.setValue( data.email.subject );
566 								},
567 								commit : function( data )
568 								{
569 									if ( !data.email )
570 										data.email = {};
571
572 									data.email.subject = this.getValue();
573 								}
574 							},
575 							{
576 								type : 'textarea',
577 								id : 'emailBody',
578 								label : editor.lang.link.emailBody,
579 								rows : 3,
580 								'default' : '',
581 								setup : function( data )
582 								{
583 									if ( data.email )
584 										this.setValue( data.email.body );
585 								},
586 								commit : function( data )
587 								{
588 									if ( !data.email )
589 										data.email = {};
590
591 									data.email.body = this.getValue();
592 								}
593 							}
594 						],
595 						setup : function( data )
596 						{
597 							if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
598 								this.getElement().hide();
599 						}
600 					}
601 				]
602 			},
603 			{
604 				id : 'target',
605 				label : editor.lang.link.target,
606 				title : editor.lang.link.target,
607 				elements :
608 				[
609 					{
610 						type : 'hbox',
611 						widths : [ '50%', '50%' ],
612 						children :
613 						[
614 							{
615 								type : 'select',
616 								id : 'linkTargetType',
617 								label : editor.lang.link.target,
618 								'default' : 'notSet',
619 								style : 'width : 100%;',
620 								'items' :
621 								[
622 									[ editor.lang.link.targetNotSet, 'notSet' ],
623 									[ editor.lang.link.targetFrame, 'frame' ],
624 									[ editor.lang.link.targetPopup, 'popup' ],
625 									[ editor.lang.link.targetNew, '_blank' ],
626 									[ editor.lang.link.targetTop, '_top' ],
627 									[ editor.lang.link.targetSelf, '_self' ],
628 									[ editor.lang.link.targetParent, '_parent' ]
629 								],
630 								onChange : targetChanged,
631 								setup : function( data )
632 								{
633 									if ( data.target )
634 										this.setValue( data.target.type );
635 								},
636 								commit : function( data )
637 								{
638 									if ( !data.target )
639 										data.target = {};
640
641 									data.target.type = this.getValue();
642 								}
643 							},
644 							{
645 								type : 'text',
646 								id : 'linkTargetName',
647 								label : editor.lang.link.targetFrameName,
648 								'default' : '',
649 								setup : function( data )
650 								{
651 									if ( data.target )
652 										this.setValue( data.target.name );
653 								},
654 								commit : function( data )
655 								{
656 									if ( !data.target )
657 										data.target = {};
658
659 									data.target.name = this.getValue();
660 								}
661 							}
662 						]
663 					},
664 					{
665 						type : 'vbox',
666 						width : 260,
667 						align : 'center',
668 						padding : 2,
669 						id : 'popupFeatures',
670 						children :
671 						[
672 							{
673 								type : 'html',
674 								html : CKEDITOR.tools.htmlEncode( editor.lang.link.popupFeatures )
675 							},
676 							{
677 								type : 'hbox',
678 								children :
679 								[
680 									{
681 										type : 'checkbox',
682 										id : 'resizable',
683 										label : editor.lang.link.popupResizable,
684 										setup : setupPopupParams,
685 										commit : commitPopupParams
686 									},
687 									{
688 										type : 'checkbox',
689 										id : 'status',
690 										label : editor.lang.link.popupStatusBar,
691 										setup : setupPopupParams,
692 										commit : commitPopupParams
693
694 									}
695 								]
696 							},
697 							{
698 								type : 'hbox',
699 								children :
700 								[
701 									{
702 										type : 'checkbox',
703 										id : 'location',
704 										label : editor.lang.link.popupLocationBar,
705 										setup : setupPopupParams,
706 										commit : commitPopupParams
707
708 									},
709 									{
710 										type : 'checkbox',
711 										id : 'toolbar',
712 										label : editor.lang.link.popupToolbar,
713 										setup : setupPopupParams,
714 										commit : commitPopupParams
715
716 									}
717 								]
718 							},
719 							{
720 								type : 'hbox',
721 								children :
722 								[
723 									{
724 										type : 'checkbox',
725 										id : 'menubar',
726 										label : editor.lang.link.popupMenuBar,
727 										setup : setupPopupParams,
728 										commit : commitPopupParams
729
730 									},
731 									{
732 										type : 'checkbox',
733 										id : 'fullscreen',
734 										label : editor.lang.link.popupFullScreen,
735 										setup : setupPopupParams,
736 										commit : commitPopupParams
737
738 									}
739 								]
740 							},
741 							{
742 								type : 'hbox',
743 								children :
744 								[
745 									{
746 										type : 'checkbox',
747 										id : 'scrollbars',
748 										label : editor.lang.link.popupScrollBars,
749 										setup : setupPopupParams,
750 										commit : commitPopupParams
751
752 									},
753 									{
754 										type : 'checkbox',
755 										id : 'dependent',
756 										label : editor.lang.link.popupDependent,
757 										setup : setupPopupParams,
758 										commit : commitPopupParams
759
760 									}
761 								]
762 							},
763 							{
764 								type : 'hbox',
765 								children :
766 								[
767 									{
768 										type :  'text',
769 										widths : [ '30%', '70%' ],
770 										labelLayout : 'horizontal',
771 										label : editor.lang.link.popupWidth,
772 										id : 'width',
773 										setup : setupPopupParams,
774 										commit : commitPopupParams
775
776 									},
777 									{
778 										type :  'text',
779 										labelLayout : 'horizontal',
780 										widths : [ '55%', '45%' ],
781 										label : editor.lang.link.popupLeft,
782 										id : 'left',
783 										setup : setupPopupParams,
784 										commit : commitPopupParams
785
786 									}
787 								]
788 							},
789 							{
790 								type : 'hbox',
791 								children :
792 								[
793 									{
794 										type :  'text',
795 										labelLayout : 'horizontal',
796 										widths : [ '30%', '70%' ],
797 										label : editor.lang.link.popupHeight,
798 										id : 'height',
799 										setup : setupPopupParams,
800 										commit : commitPopupParams
801
802 									},
803 									{
804 										type :  'text',
805 										labelLayout : 'horizontal',
806 										label : editor.lang.link.popupTop,
807 										widths : [ '55%', '45%' ],
808 										id : 'top',
809 										setup : setupPopupParams,
810 										commit : commitPopupParams
811
812 									}
813 								]
814 							}
815 						]
816 					}
817 				]
818 			},
819 			{
820 				id : 'upload',
821 				label : editor.lang.link.upload,
822 				title : editor.lang.link.upload,
823 				hidden : true,
824 				filebrowser : 'uploadButton',
825 				elements :
826 				[
827 					{
828 						type : 'file',
829 						id : 'upload',
830 						label : editor.lang.common.upload,
831 						size : 38
832 					},
833 					{
834 						type : 'fileButton',
835 						id : 'uploadButton',
836 						label : editor.lang.common.uploadSubmit,
837 						filebrowser : 'info:url',
838 						'for' : [ 'upload', 'upload' ]
839 					}
840 				]
841 			},
842 			{
843 				id : 'advanced',
844 				label : editor.lang.link.advanced,
845 				title : editor.lang.link.advanced,
846 				elements :
847 				[
848 					{
849 						type : 'vbox',
850 						padding : 1,
851 						children :
852 						[
853 							{
854 								type : 'hbox',
855 								widths : [ '45%', '35%', '20%' ],
856 								children :
857 								[
858 									{
859 										type : 'text',
860 										id : 'advId',
861 										label : editor.lang.link.id,
862 										setup : setupAdvParams,
863 										commit : commitAdvParams
864 									},
865 									{
866 										type : 'select',
867 										id : 'advLangDir',
868 										label : editor.lang.link.langDir,
869 										'default' : '',
870 										style : 'width: 100%;',
871 										items :
872 										[
873 											[ editor.lang.link.langDirNotSet, '' ],
874 											[ editor.lang.link.langDirLTR, 'ltr' ],
875 											[ editor.lang.link.langDirRTL, 'rtl' ]
876 										],
877 										setup : setupAdvParams,
878 										commit : commitAdvParams
879 									},
880 									{
881 										type : 'text',
882 										id : 'advAccessKey',
883 										label : editor.lang.link.acccessKey,
884 										maxLength : 1,
885 										setup : setupAdvParams,
886 										commit : commitAdvParams
887
888 									}
889 								]
890 							},
891 							{
892 								type : 'hbox',
893 								widths : [ '45%', '35%', '20%' ],
894 								children :
895 								[
896 									{
897 										type : 'text',
898 										label : editor.lang.link.name,
899 										id : 'advName',
900 										setup : setupAdvParams,
901 										commit : commitAdvParams
902
903 									},
904 									{
905 										type : 'text',
906 										label : editor.lang.link.langCode,
907 										id : 'advLangCode',
908 										'default' : '',
909 										setup : setupAdvParams,
910 										commit : commitAdvParams
911
912 									},
913 									{
914 										type : 'text',
915 										label : editor.lang.link.tabIndex,
916 										id : 'advTabIndex',
917 										maxLength : 5,
918 										setup : setupAdvParams,
919 										commit : commitAdvParams
920
921 									}
922 								]
923 							}
924 						]
925 					},
926 					{
927 						type : 'vbox',
928 						padding : 1,
929 						children :
930 						[
931 							{
932 								type : 'hbox',
933 								widths : [ '45%', '55%' ],
934 								children :
935 								[
936 									{
937 										type : 'text',
938 										label : editor.lang.link.advisoryTitle,
939 										'default' : '',
940 										id : 'advTitle',
941 										setup : setupAdvParams,
942 										commit : commitAdvParams
943
944 									},
945 									{
946 										type : 'text',
947 										label : editor.lang.link.advisoryContentType,
948 										'default' : '',
949 										id : 'advContentType',
950 										setup : setupAdvParams,
951 										commit : commitAdvParams
952
953 									}
954 								]
955 							},
956 							{
957 								type : 'hbox',
958 								widths : [ '45%', '55%' ],
959 								children :
960 								[
961 									{
962 										type : 'text',
963 										label : editor.lang.link.cssClasses,
964 										'default' : '',
965 										id : 'advCSSClasses',
966 										setup : setupAdvParams,
967 										commit : commitAdvParams
968
969 									},
970 									{
971 										type : 'text',
972 										label : editor.lang.link.charset,
973 										'default' : '',
974 										id : 'advCharset',
975 										setup : setupAdvParams,
976 										commit : commitAdvParams
977
978 									}
979 								]
980 							},
981 							{
982 								type : 'hbox',
983 								children :
984 								[
985 									{
986 										type : 'text',
987 										label : editor.lang.link.styles,
988 										'default' : '',
989 										id : 'advStyles',
990 										setup : setupAdvParams,
991 										commit : commitAdvParams
992
993 									}
994 								]
995 							}
996 						]
997 					}
998 				]
999 			}
1000 		],
1001 		onShow : function()
1002 		{
1003 			this.fakeObj = false;
1004
1005 			var editor = this.getParentEditor(),
1006 				selection = editor.getSelection(),
1007 				ranges = selection.getRanges(),
1008 				element = null,
1009 				me = this;
1010 			// Fill in all the relevant fields if there's already one link selected.
1011 			if ( ranges.length == 1 )
1012 			{
1013
1014 				var rangeRoot = ranges[0].getCommonAncestor( true );
1015 				element = rangeRoot.getAscendant( 'a', true );
1016 				if ( element && element.getAttribute( 'href' ) )
1017 				{
1018 					selection.selectElement( element );
1019 				}
1020 				else
1021 				{
1022 					element = rangeRoot.getAscendant( 'img', true );
1023 					if ( element && element.getAttribute( '_cke_real_element_type' ) && element.getAttribute( '_cke_real_element_type' ) == 'anchor' )
1024 					{
1025 						this.fakeObj = element;
1026 						element = editor.restoreRealElement( this.fakeObj );
1027 						selection.selectElement( this.fakeObj );
1028 					}
1029 				}
1030 			}
1031
1032 			this.setupContent( parseLink.apply( this, [ editor, element ] ) );
1033 		},
1034 		onOk : function()
1035 		{
1036 			var attributes = { href : 'javascript:void(0)/*' + CKEDITOR.tools.getNextNumber() + '*/' },
1037 				removeAttributes = [],
1038 				data = { href : attributes.href },
1039 				me = this, editor = this.getParentEditor();
1040
1041 			this.commitContent( data );
1042
1043 			// Compose the URL.
1044 			switch ( data.type || 'url' )
1045 			{
1046 				case 'url':
1047 					var protocol = ( data.url && data.url.protocol != undefined ) ? data.url.protocol : 'http://',
1048 						url = ( data.url && data.url.url ) || '';
1049 					attributes._cke_saved_href = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url;
1050 					break;
1051 				case 'anchor':
1052 					var name = ( data.anchor && data.anchor.name ),
1053 						id = ( data.anchor && data.anchor.id );
1054 					attributes._cke_saved_href = '#' + ( name || id || '' );
1055 					break;
1056 				case 'email':
1057 					var address = ( data.email && data.email.address ),
1058 						subject = ( data.email && encodeURIComponent( data.email.subject || '' ) ),
1059 						body = ( data.email && encodeURIComponent( data.email.body || '' ) ),
1060 						linkList = [ 'mailto:', address ];
1061 					if ( subject || body )
1062 					{
1063 						var argList = [];
1064 						linkList.push( '?' );
1065 						subject && argList.push( 'subject=' + subject );
1066 						body && argList.push( 'body=' + body );
1067 						linkList.push( argList.join( '&' ) );
1068 					}
1069 					attributes._cke_saved_href = linkList.join( '' );
1070 					break;
1071 				default:
1072 			}
1073
1074 			// Popups and target.
1075 			if ( data.target )
1076 			{
1077 				if ( data.target.type == 'popup' )
1078 				{
1079 					var onclickList = [ 'window.open(this.href, \'',
1080 							data.target.name || '', '\', \'' ];
1081 					var featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen',
1082 							'scrollbars', 'dependent' ];
1083 					var featureLength = featureList.length;
1084 					var addFeature = function( featureName )
1085 					{
1086 						if ( data.target[ featureName ] )
1087 							featureList.push( featureName + '=' + data.target[ featureName ] );
1088 					};
1089
1090 					for ( var i = 0 ; i < featureLength ; i++ )
1091 						featureList[i] = featureList[i] + ( data.target[ featureList[i] ] ? '=yes' : '=no' ) ;
1092 					addFeature( 'width' );
1093 					addFeature( 'left' );
1094 					addFeature( 'height' );
1095 					addFeature( 'top' );
1096
1097 					onclickList.push( featureList.join( ',' ), '\'); return false;' );
1098 					attributes[ CKEDITOR.env.ie || CKEDITOR.env.webkit ? '_cke_pa_onclick' : 'onclick' ] = onclickList.join( '' );
1099 				}
1100 				else
1101 				{
1102 					if ( data.target.type != 'notSet' && data.target.name )
1103 						attributes.target = data.target.name;
1104 					removeAttributes.push( '_cke_pa_onclick', 'onclick' );
1105 				}
1106 			}
1107
1108 			// Advanced attributes.
1109 			if ( data.adv )
1110 			{
1111 				var advAttr = function( inputName, attrName )
1112 				{
1113 					var value = data.adv[ inputName ];
1114 					if ( value )
1115 						attributes[attrName] = value;
1116 					else
1117 						removeAttributes.push( attrName );
1118 				};
1119
1120 				if ( this._.selectedElement )
1121 					advAttr( 'advId', 'id' );
1122 				advAttr( 'advLangDir', 'dir' );
1123 				advAttr( 'advAccessKey', 'accessKey' );
1124 				advAttr( 'advName', 'name' );
1125 				advAttr( 'advLangCode', 'lang' );
1126 				advAttr( 'advTabIndex', 'tabindex' );
1127 				advAttr( 'advTitle', 'title' );
1128 				advAttr( 'advContentType', 'type' );
1129 				advAttr( 'advCSSClasses', 'class' );
1130 				advAttr( 'advCharset', 'charset' );
1131 				advAttr( 'advStyles', 'style' );
1132 			}
1133
1134 			if ( !this._.selectedElement )
1135 			{
1136 				// Create element if current selection is collapsed.
1137 				var selection = editor.getSelection(),
1138 					ranges = selection.getRanges();
1139 				if ( ranges.length == 1 && ranges[0].collapsed )
1140 				{
1141 					var text = new CKEDITOR.dom.text( attributes._cke_saved_href, editor.document );
1142 					ranges[0].insertNode( text );
1143 					ranges[0].selectNodeContents( text );
1144 					selection.selectRanges( ranges );
1145 				}
1146
1147 				// Apply style.
1148 				var style = new CKEDITOR.style( { element : 'a', attributes : attributes } );
1149 				style.type = CKEDITOR.STYLE_INLINE;		// need to override... dunno why.
1150 				style.apply( editor.document );
1151
1152 				// Id. Apply only to the first link.
1153 				if ( data.adv && data.adv.advId )
1154 				{
1155 					var links = this.getParentEditor().document.$.getElementsByTagName( 'a' );
1156 					for ( i = 0 ; i < links.length ; i++ )
1157 					{
1158 						if ( links[i].href == attributes.href )
1159 						{
1160 							links[i].id = data.adv.advId;
1161 							break;
1162 						}
1163 					}
1164 				}
1165 			}
1166 			else
1167 			{
1168 				// We're only editing an existing link, so just overwrite the attributes.
1169 				var element = this._.selectedElement;
1170
1171 				// IE BUG: Setting the name attribute to an existing link doesn't work.
1172 				// Must re-create the link from weired syntax to workaround.
1173 				if ( CKEDITOR.env.ie && attributes.name != element.getAttribute( 'name' ) )
1174 				{
1175 					var newElement = new CKEDITOR.dom.element( '<a name="' + CKEDITOR.tools.htmlEncode( attributes.name ) + '">',
1176 							editor.document );
1177
1178 					selection = editor.getSelection();
1179
1180 					element.moveChildren( newElement );
1181 					element.copyAttributes( newElement, { name : 1 } );
1182 					newElement.replace( element );
1183 					element = newElement;
1184
1185 					selection.selectElement( element );
1186 				}
1187
1188 				element.setAttributes( attributes );
1189 				element.removeAttributes( removeAttributes );
1190
1191 				// Make the element display as an anchor if a name has been set.
1192 				if ( element.getAttribute( 'name' ) )
1193 					element.addClass( 'cke_anchor' );
1194 				else
1195 					element.removeClass( 'cke_anchor' );
1196
1197 				if ( this.fakeObj )
1198 					editor.createFakeElement( element, 'cke_anchor', 'anchor' ).replace( this.fakeObj );
1199
1200 				delete this._.selectedElement;
1201 			}
1202 		},
1203 		onLoad : function()
1204 		{
1205 			if ( !editor.config.linkShowAdvancedTab )
1206 				this.hidePage( 'advanced' );		//Hide Advanded tab.
1207
1208 			if ( !editor.config.linkShowTargetTab )
1209 				this.hidePage( 'target' );		//Hide Target tab.
1210
1211 		}
1212 	};
1213 } );
1214