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 CKEDITOR.dialog.add( 'link', function( editor ) 7 { 8 var plugin = CKEDITOR.plugins.link; 9 // Handles the event when the "Target" selection box is changed. 10 var targetChanged = function() 11 { 12 var dialog = this.getDialog(), 13 popupFeatures = dialog.getContentElement( 'target', 'popupFeatures' ), 14 targetName = dialog.getContentElement( 'target', 'linkTargetName' ), 15 value = this.getValue(); 16 17 if ( !popupFeatures || !targetName ) 18 return; 19 20 popupFeatures = popupFeatures.getElement(); 21 popupFeatures.hide(); 22 targetName.setValue( '' ); 23 24 switch ( value ) 25 { 26 case 'frame' : 27 targetName.setLabel( editor.lang.link.targetFrameName ); 28 targetName.getElement().show(); 29 break; 30 case 'popup' : 31 popupFeatures.show(); 32 targetName.setLabel( editor.lang.link.targetPopupName ); 33 targetName.getElement().show(); 34 break; 35 default : 36 targetName.setValue( value ); 37 targetName.getElement().hide(); 38 break; 39 } 40 41 }; 42 43 // Handles the event when the "Type" selection box is changed. 44 var linkTypeChanged = function() 45 { 46 var dialog = this.getDialog(), 47 partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ], 48 typeValue = this.getValue(), 49 uploadTab = dialog.definition.getContents( 'upload' ), 50 uploadInitiallyHidden = uploadTab && uploadTab.hidden; 51 52 if ( typeValue == 'url' ) 53 { 54 if ( editor.config.linkShowTargetTab ) 55 dialog.showPage( 'target' ); 56 if ( !uploadInitiallyHidden ) 57 dialog.showPage( 'upload' ); 58 } 59 else 60 { 61 dialog.hidePage( 'target' ); 62 if ( !uploadInitiallyHidden ) 63 dialog.hidePage( 'upload' ); 64 } 65 66 for ( var i = 0 ; i < partIds.length ; i++ ) 67 { 68 var element = dialog.getContentElement( 'info', partIds[i] ); 69 if ( !element ) 70 continue; 71 72 element = element.getElement().getParent().getParent(); 73 if ( partIds[i] == typeValue + 'Options' ) 74 element.show(); 75 else 76 element.hide(); 77 } 78 79 dialog.layout(); 80 }; 81 82 // Loads the parameters in a selected link to the link dialog fields. 83 var javascriptProtocolRegex = /^javascript:/, 84 emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/, 85 emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/, 86 emailBodyRegex = /body=([^;?:@&=$,\/]*)/, 87 anchorRegex = /^#(.*)$/, 88 urlRegex = /^((?:http|https|ftp|news):\/\/)?(.*)$/, 89 selectableTargets = /^(_(?:self|top|parent|blank))$/, 90 encodedEmailLinkRegex = /^javascript:void\(location\.href='mailto:'\+String\.fromCharCode\(([^)]+)\)(?:\+'(.*)')?\)$/, 91 functionCallProtectedEmailLinkRegex = /^javascript:([^(]+)\(([^)]+)\)$/; 92 93 var popupRegex = 94 /\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/; 95 var popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi; 96 97 var parseLink = function( editor, element ) 98 { 99 var href = ( element && ( element.data( 'cke-saved-href' ) || element.getAttribute( 'href' ) ) ) || '', 100 javascriptMatch, 101 emailMatch, 102 anchorMatch, 103 urlMatch, 104 retval = {}; 105 106 if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) ) 107 { 108 if ( emailProtection == 'encode' ) 109 { 110 href = href.replace( encodedEmailLinkRegex, 111 function ( match, protectedAddress, rest ) 112 { 113 return 'mailto:' + 114 String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) + 115 ( rest && unescapeSingleQuote( rest ) ); 116 }); 117 } 118 // Protected email link as function call. 119 else if ( emailProtection ) 120 { 121 href.replace( functionCallProtectedEmailLinkRegex, function( match, funcName, funcArgs ) 122 { 123 if ( funcName == compiledProtectionFunction.name ) 124 { 125 retval.type = 'email'; 126 var email = retval.email = {}; 127 128 var paramRegex = /[^,\s]+/g, 129 paramQuoteRegex = /(^')|('$)/g, 130 paramsMatch = funcArgs.match( paramRegex ), 131 paramsMatchLength = paramsMatch.length, 132 paramName, 133 paramVal; 134 135 for ( var i = 0; i < paramsMatchLength; i++ ) 136 { 137 paramVal = decodeURIComponent( unescapeSingleQuote( paramsMatch[ i ].replace( paramQuoteRegex, '' ) ) ); 138 paramName = compiledProtectionFunction.params[ i ].toLowerCase(); 139 email[ paramName ] = paramVal; 140 } 141 email.address = [ email.name, email.domain ].join( '@' ); 142 } 143 } ); 144 } 145 } 146 147 if ( !retval.type ) 148 { 149 if ( ( anchorMatch = href.match( anchorRegex ) ) ) 150 { 151 retval.type = 'anchor'; 152 retval.anchor = {}; 153 retval.anchor.name = retval.anchor.id = anchorMatch[1]; 154 } 155 // Protected email link as encoded string. 156 else if ( ( emailMatch = href.match( emailRegex ) ) ) 157 { 158 var subjectMatch = href.match( emailSubjectRegex ), 159 bodyMatch = href.match( emailBodyRegex ); 160 161 retval.type = 'email'; 162 var email = ( retval.email = {} ); 163 email.address = emailMatch[ 1 ]; 164 subjectMatch && ( email.subject = decodeURIComponent( subjectMatch[ 1 ] ) ); 165 bodyMatch && ( email.body = decodeURIComponent( bodyMatch[ 1 ] ) ); 166 } 167 // urlRegex matches empty strings, so need to check for href as well. 168 else if ( href && ( urlMatch = href.match( urlRegex ) ) ) 169 { 170 retval.type = 'url'; 171 retval.url = {}; 172 retval.url.protocol = urlMatch[1]; 173 retval.url.url = urlMatch[2]; 174 } 175 else 176 retval.type = 'url'; 177 } 178 179 // Load target and popup settings. 180 if ( element ) 181 { 182 var target = element.getAttribute( 'target' ); 183 retval.target = {}; 184 retval.adv = {}; 185 186 // IE BUG: target attribute is an empty string instead of null in IE if it's not set. 187 if ( !target ) 188 { 189 var onclick = element.data( 'cke-pa-onclick' ) || element.getAttribute( 'onclick' ), 190 onclickMatch = onclick && onclick.match( popupRegex ); 191 if ( onclickMatch ) 192 { 193 retval.target.type = 'popup'; 194 retval.target.name = onclickMatch[1]; 195 196 var featureMatch; 197 while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[2] ) ) ) 198 { 199 // Some values should remain numbers (#7300) 200 if ( ( featureMatch[2] == 'yes' || featureMatch[2] == '1' ) && !( featureMatch[1] in { height:1, width:1, top:1, left:1 } ) ) 201 retval.target[ featureMatch[1] ] = true; 202 else if ( isFinite( featureMatch[2] ) ) 203 retval.target[ featureMatch[1] ] = featureMatch[2]; 204 } 205 } 206 } 207 else 208 { 209 var targetMatch = target.match( selectableTargets ); 210 if ( targetMatch ) 211 retval.target.type = retval.target.name = target; 212 else 213 { 214 retval.target.type = 'frame'; 215 retval.target.name = target; 216 } 217 } 218 219 var me = this; 220 var advAttr = function( inputName, attrName ) 221 { 222 var value = element.getAttribute( attrName ); 223 if ( value !== null ) 224 retval.adv[ inputName ] = value || ''; 225 }; 226 advAttr( 'advId', 'id' ); 227 advAttr( 'advLangDir', 'dir' ); 228 advAttr( 'advAccessKey', 'accessKey' ); 229 230 retval.adv.advName = 231 element.data( 'cke-saved-name' ) 232 || element.getAttribute( 'name' ) 233 || ''; 234 advAttr( 'advLangCode', 'lang' ); 235 advAttr( 'advTabIndex', 'tabindex' ); 236 advAttr( 'advTitle', 'title' ); 237 advAttr( 'advContentType', 'type' ); 238 CKEDITOR.plugins.link.synAnchorSelector ? 239 retval.adv.advCSSClasses = getLinkClass( element ) 240 : advAttr( 'advCSSClasses', 'class' ); 241 advAttr( 'advCharset', 'charset' ); 242 advAttr( 'advStyles', 'style' ); 243 advAttr( 'advRel', 'rel' ); 244 } 245 246 // Find out whether we have any anchors in the editor. 247 var anchors = retval.anchors = [], 248 i, count, item; 249 250 // For some browsers we set contenteditable="false" on anchors, making document.anchors not to include them, so we must traverse the links manually (#7893). 251 if ( CKEDITOR.plugins.link.emptyAnchorFix ) 252 { 253 var links = editor.document.getElementsByTag( 'a' ); 254 for ( i = 0, count = links.count(); i < count; i++ ) 255 { 256 item = links.getItem( i ); 257 if ( item.data( 'cke-saved-name' ) || item.hasAttribute( 'name' ) ) 258 anchors.push( { name : item.data( 'cke-saved-name' ) || item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) } ); 259 } 260 } 261 else 262 { 263 var anchorList = new CKEDITOR.dom.nodeList( editor.document.$.anchors ); 264 for ( i = 0, count = anchorList.count(); i < count; i++ ) 265 { 266 item = anchorList.getItem( i ); 267 anchors[ i ] = { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) }; 268 } 269 } 270 271 if ( CKEDITOR.plugins.link.fakeAnchor ) 272 { 273 var imgs = editor.document.getElementsByTag( 'img' ); 274 for ( i = 0, count = imgs.count(); i < count; i++ ) 275 { 276 if ( ( item = CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, imgs.getItem( i ) ) ) ) 277 anchors.push( { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) } ); 278 } 279 } 280 281 // Record down the selected element in the dialog. 282 this._.selectedElement = element; 283 return retval; 284 }; 285 286 var setupParams = function( page, data ) 287 { 288 if ( data[page] ) 289 this.setValue( data[page][this.id] || '' ); 290 }; 291 292 var setupPopupParams = function( data ) 293 { 294 return setupParams.call( this, 'target', data ); 295 }; 296 297 var setupAdvParams = function( data ) 298 { 299 return setupParams.call( this, 'adv', data ); 300 }; 301 302 var commitParams = function( page, data ) 303 { 304 if ( !data[page] ) 305 data[page] = {}; 306 307 data[page][this.id] = this.getValue() || ''; 308 }; 309 310 var commitPopupParams = function( data ) 311 { 312 return commitParams.call( this, 'target', data ); 313 }; 314 315 var commitAdvParams = function( data ) 316 { 317 return commitParams.call( this, 'adv', data ); 318 }; 319 320 function unescapeSingleQuote( str ) 321 { 322 return str.replace( /\\'/g, '\'' ); 323 } 324 325 function escapeSingleQuote( str ) 326 { 327 return str.replace( /'/g, '\\$&' ); 328 } 329 330 var emailProtection = editor.config.emailProtection || ''; 331 332 // Compile the protection function pattern. 333 if ( emailProtection && emailProtection != 'encode' ) 334 { 335 var compiledProtectionFunction = {}; 336 337 emailProtection.replace( /^([^(]+)\(([^)]+)\)$/, function( match, funcName, params ) 338 { 339 compiledProtectionFunction.name = funcName; 340 compiledProtectionFunction.params = []; 341 params.replace( /[^,\s]+/g, function( param ) 342 { 343 compiledProtectionFunction.params.push( param ); 344 } ); 345 } ); 346 } 347 348 function protectEmailLinkAsFunction( email ) 349 { 350 var retval, 351 name = compiledProtectionFunction.name, 352 params = compiledProtectionFunction.params, 353 paramName, 354 paramValue; 355 356 retval = [ name, '(' ]; 357 for ( var i = 0; i < params.length; i++ ) 358 { 359 paramName = params[ i ].toLowerCase(); 360 paramValue = email[ paramName ]; 361 362 i > 0 && retval.push( ',' ); 363 retval.push( '\'', 364 paramValue ? 365 escapeSingleQuote( encodeURIComponent( email[ paramName ] ) ) 366 : '', 367 '\''); 368 } 369 retval.push( ')' ); 370 return retval.join( '' ); 371 } 372 373 function protectEmailAddressAsEncodedString( address ) 374 { 375 var charCode, 376 length = address.length, 377 encodedChars = []; 378 for ( var i = 0; i < length; i++ ) 379 { 380 charCode = address.charCodeAt( i ); 381 encodedChars.push( charCode ); 382 } 383 return 'String.fromCharCode(' + encodedChars.join( ',' ) + ')'; 384 } 385 386 function getLinkClass( ele ) 387 { 388 var className = ele.getAttribute( 'class' ); 389 return className ? className.replace( /\s*(?:cke_anchor_empty|cke_anchor)(?:\s*$)?/g, '' ) : ''; 390 } 391 392 var commonLang = editor.lang.common, 393 linkLang = editor.lang.link; 394 395 return { 396 title : linkLang.title, 397 minWidth : 350, 398 minHeight : 230, 399 contents : [ 400 { 401 id : 'info', 402 label : linkLang.info, 403 title : linkLang.info, 404 elements : 405 [ 406 { 407 id : 'linkType', 408 type : 'select', 409 label : linkLang.type, 410 'default' : 'url', 411 items : 412 [ 413 [ linkLang.toUrl, 'url' ], 414 [ linkLang.toAnchor, 'anchor' ], 415 [ linkLang.toEmail, 'email' ] 416 ], 417 onChange : linkTypeChanged, 418 setup : function( data ) 419 { 420 if ( data.type ) 421 this.setValue( data.type ); 422 }, 423 commit : function( data ) 424 { 425 data.type = this.getValue(); 426 } 427 }, 428 { 429 type : 'vbox', 430 id : 'urlOptions', 431 children : 432 [ 433 { 434 type : 'hbox', 435 widths : [ '25%', '75%' ], 436 children : 437 [ 438 { 439 id : 'protocol', 440 type : 'select', 441 label : commonLang.protocol, 442 'default' : 'http://', 443 items : 444 [ 445 // Force 'ltr' for protocol names in BIDI. (#5433) 446 [ 'http://\u200E', 'http://' ], 447 [ 'https://\u200E', 'https://' ], 448 [ 'ftp://\u200E', 'ftp://' ], 449 [ 'news://\u200E', 'news://' ], 450 [ linkLang.other , '' ] 451 ], 452 setup : function( data ) 453 { 454 if ( data.url ) 455 this.setValue( data.url.protocol || '' ); 456 }, 457 commit : function( data ) 458 { 459 if ( !data.url ) 460 data.url = {}; 461 462 data.url.protocol = this.getValue(); 463 } 464 }, 465 { 466 type : 'text', 467 id : 'url', 468 label : commonLang.url, 469 required: true, 470 onLoad : function () 471 { 472 this.allowOnChange = true; 473 }, 474 onKeyUp : function() 475 { 476 this.allowOnChange = false; 477 var protocolCmb = this.getDialog().getContentElement( 'info', 'protocol' ), 478 url = this.getValue(), 479 urlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/i, 480 urlOnChangeTestOther = /^((javascript:)|[#\/\.\?])/i; 481 482 var protocol = urlOnChangeProtocol.exec( url ); 483 if ( protocol ) 484 { 485 this.setValue( url.substr( protocol[ 0 ].length ) ); 486 protocolCmb.setValue( protocol[ 0 ].toLowerCase() ); 487 } 488 else if ( urlOnChangeTestOther.test( url ) ) 489 protocolCmb.setValue( '' ); 490 491 this.allowOnChange = true; 492 }, 493 onChange : function() 494 { 495 if ( this.allowOnChange ) // Dont't call on dialog load. 496 this.onKeyUp(); 497 }, 498 validate : function() 499 { 500 var dialog = this.getDialog(); 501 502 if ( dialog.getContentElement( 'info', 'linkType' ) && 503 dialog.getValueOf( 'info', 'linkType' ) != 'url' ) 504 return true; 505 506 if ( (/javascript\:/).test( this.getValue() ) ) { 507 alert( commonLang.invalidValue ); 508 return false; 509 } 510 511 if ( this.getDialog().fakeObj ) // Edit Anchor. 512 return true; 513 514 var func = CKEDITOR.dialog.validate.notEmpty( linkLang.noUrl ); 515 return func.apply( this ); 516 }, 517 setup : function( data ) 518 { 519 this.allowOnChange = false; 520 if ( data.url ) 521 this.setValue( data.url.url ); 522 this.allowOnChange = true; 523 524 }, 525 commit : function( data ) 526 { 527 // IE will not trigger the onChange event if the mouse has been used 528 // to carry all the operations #4724 529 this.onChange(); 530 531 if ( !data.url ) 532 data.url = {}; 533 534 data.url.url = this.getValue(); 535 this.allowOnChange = false; 536 } 537 } 538 ], 539 setup : function( data ) 540 { 541 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) 542 this.getElement().show(); 543 } 544 }, 545 { 546 type : 'button', 547 id : 'browse', 548 hidden : 'true', 549 filebrowser : 'info:url', 550 label : commonLang.browseServer 551 } 552 ] 553 }, 554 { 555 type : 'vbox', 556 id : 'anchorOptions', 557 width : 260, 558 align : 'center', 559 padding : 0, 560 children : 561 [ 562 { 563 type : 'fieldset', 564 id : 'selectAnchorText', 565 label : linkLang.selectAnchor, 566 setup : function( data ) 567 { 568 if ( data.anchors.length > 0 ) 569 this.getElement().show(); 570 else 571 this.getElement().hide(); 572 }, 573 children : 574 [ 575 { 576 type : 'hbox', 577 id : 'selectAnchor', 578 children : 579 [ 580 { 581 type : 'select', 582 id : 'anchorName', 583 'default' : '', 584 label : linkLang.anchorName, 585 style : 'width: 100%;', 586 items : 587 [ 588 [ '' ] 589 ], 590 setup : function( data ) 591 { 592 this.clear(); 593 this.add( '' ); 594 for ( var i = 0 ; i < data.anchors.length ; i++ ) 595 { 596 if ( data.anchors[i].name ) 597 this.add( data.anchors[i].name ); 598 } 599 600 if ( data.anchor ) 601 this.setValue( data.anchor.name ); 602 603 var linkType = this.getDialog().getContentElement( 'info', 'linkType' ); 604 if ( linkType && linkType.getValue() == 'email' ) 605 this.focus(); 606 }, 607 commit : function( data ) 608 { 609 if ( !data.anchor ) 610 data.anchor = {}; 611 612 data.anchor.name = this.getValue(); 613 } 614 }, 615 { 616 type : 'select', 617 id : 'anchorId', 618 'default' : '', 619 label : linkLang.anchorId, 620 style : 'width: 100%;', 621 items : 622 [ 623 [ '' ] 624 ], 625 setup : function( data ) 626 { 627 this.clear(); 628 this.add( '' ); 629 for ( var i = 0 ; i < data.anchors.length ; i++ ) 630 { 631 if ( data.anchors[i].id ) 632 this.add( data.anchors[i].id ); 633 } 634 635 if ( data.anchor ) 636 this.setValue( data.anchor.id ); 637 }, 638 commit : function( data ) 639 { 640 if ( !data.anchor ) 641 data.anchor = {}; 642 643 data.anchor.id = this.getValue(); 644 } 645 } 646 ], 647 setup : function( data ) 648 { 649 if ( data.anchors.length > 0 ) 650 this.getElement().show(); 651 else 652 this.getElement().hide(); 653 } 654 } 655 ] 656 }, 657 { 658 type : 'html', 659 id : 'noAnchors', 660 style : 'text-align: center;', 661 html : '<div role="note" tabIndex="-1">' + CKEDITOR.tools.htmlEncode( linkLang.noAnchors ) + '</div>', 662 // Focus the first element defined in above html. 663 focus : true, 664 setup : function( data ) 665 { 666 if ( data.anchors.length < 1 ) 667 this.getElement().show(); 668 else 669 this.getElement().hide(); 670 } 671 } 672 ], 673 setup : function( data ) 674 { 675 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) 676 this.getElement().hide(); 677 } 678 }, 679 { 680 type : 'vbox', 681 id : 'emailOptions', 682 padding : 1, 683 children : 684 [ 685 { 686 type : 'text', 687 id : 'emailAddress', 688 label : linkLang.emailAddress, 689 required : true, 690 validate : function() 691 { 692 var dialog = this.getDialog(); 693 694 if ( !dialog.getContentElement( 'info', 'linkType' ) || 695 dialog.getValueOf( 'info', 'linkType' ) != 'email' ) 696 return true; 697 698 var func = CKEDITOR.dialog.validate.notEmpty( linkLang.noEmail ); 699 return func.apply( this ); 700 }, 701 setup : function( data ) 702 { 703 if ( data.email ) 704 this.setValue( data.email.address ); 705 706 var linkType = this.getDialog().getContentElement( 'info', 'linkType' ); 707 if ( linkType && linkType.getValue() == 'email' ) 708 this.select(); 709 }, 710 commit : function( data ) 711 { 712 if ( !data.email ) 713 data.email = {}; 714 715 data.email.address = this.getValue(); 716 } 717 }, 718 { 719 type : 'text', 720 id : 'emailSubject', 721 label : linkLang.emailSubject, 722 setup : function( data ) 723 { 724 if ( data.email ) 725 this.setValue( data.email.subject ); 726 }, 727 commit : function( data ) 728 { 729 if ( !data.email ) 730 data.email = {}; 731 732 data.email.subject = this.getValue(); 733 } 734 }, 735 { 736 type : 'textarea', 737 id : 'emailBody', 738 label : linkLang.emailBody, 739 rows : 3, 740 'default' : '', 741 setup : function( data ) 742 { 743 if ( data.email ) 744 this.setValue( data.email.body ); 745 }, 746 commit : function( data ) 747 { 748 if ( !data.email ) 749 data.email = {}; 750 751 data.email.body = this.getValue(); 752 } 753 } 754 ], 755 setup : function( data ) 756 { 757 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) 758 this.getElement().hide(); 759 } 760 } 761 ] 762 }, 763 { 764 id : 'target', 765 label : linkLang.target, 766 title : linkLang.target, 767 elements : 768 [ 769 { 770 type : 'hbox', 771 widths : [ '50%', '50%' ], 772 children : 773 [ 774 { 775 type : 'select', 776 id : 'linkTargetType', 777 label : commonLang.target, 778 'default' : 'notSet', 779 style : 'width : 100%;', 780 'items' : 781 [ 782 [ commonLang.notSet, 'notSet' ], 783 [ linkLang.targetFrame, 'frame' ], 784 [ linkLang.targetPopup, 'popup' ], 785 [ commonLang.targetNew, '_blank' ], 786 [ commonLang.targetTop, '_top' ], 787 [ commonLang.targetSelf, '_self' ], 788 [ commonLang.targetParent, '_parent' ] 789 ], 790 onChange : targetChanged, 791 setup : function( data ) 792 { 793 if ( data.target ) 794 this.setValue( data.target.type || 'notSet' ); 795 targetChanged.call( this ); 796 }, 797 commit : function( data ) 798 { 799 if ( !data.target ) 800 data.target = {}; 801 802 data.target.type = this.getValue(); 803 } 804 }, 805 { 806 type : 'text', 807 id : 'linkTargetName', 808 label : linkLang.targetFrameName, 809 'default' : '', 810 setup : function( data ) 811 { 812 if ( data.target ) 813 this.setValue( data.target.name ); 814 }, 815 commit : function( data ) 816 { 817 if ( !data.target ) 818 data.target = {}; 819 820 data.target.name = this.getValue().replace(/\W/gi, ''); 821 } 822 } 823 ] 824 }, 825 { 826 type : 'vbox', 827 width : '100%', 828 align : 'center', 829 padding : 2, 830 id : 'popupFeatures', 831 children : 832 [ 833 { 834 type : 'fieldset', 835 label : linkLang.popupFeatures, 836 children : 837 [ 838 { 839 type : 'hbox', 840 children : 841 [ 842 { 843 type : 'checkbox', 844 id : 'resizable', 845 label : linkLang.popupResizable, 846 setup : setupPopupParams, 847 commit : commitPopupParams 848 }, 849 { 850 type : 'checkbox', 851 id : 'status', 852 label : linkLang.popupStatusBar, 853 setup : setupPopupParams, 854 commit : commitPopupParams 855 856 } 857 ] 858 }, 859 { 860 type : 'hbox', 861 children : 862 [ 863 { 864 type : 'checkbox', 865 id : 'location', 866 label : linkLang.popupLocationBar, 867 setup : setupPopupParams, 868 commit : commitPopupParams 869 870 }, 871 { 872 type : 'checkbox', 873 id : 'toolbar', 874 label : linkLang.popupToolbar, 875 setup : setupPopupParams, 876 commit : commitPopupParams 877 878 } 879 ] 880 }, 881 { 882 type : 'hbox', 883 children : 884 [ 885 { 886 type : 'checkbox', 887 id : 'menubar', 888 label : linkLang.popupMenuBar, 889 setup : setupPopupParams, 890 commit : commitPopupParams 891 892 }, 893 { 894 type : 'checkbox', 895 id : 'fullscreen', 896 label : linkLang.popupFullScreen, 897 setup : setupPopupParams, 898 commit : commitPopupParams 899 900 } 901 ] 902 }, 903 { 904 type : 'hbox', 905 children : 906 [ 907 { 908 type : 'checkbox', 909 id : 'scrollbars', 910 label : linkLang.popupScrollBars, 911 setup : setupPopupParams, 912 commit : commitPopupParams 913 914 }, 915 { 916 type : 'checkbox', 917 id : 'dependent', 918 label : linkLang.popupDependent, 919 setup : setupPopupParams, 920 commit : commitPopupParams 921 922 } 923 ] 924 }, 925 { 926 type : 'hbox', 927 children : 928 [ 929 { 930 type : 'text', 931 widths : [ '50%', '50%' ], 932 labelLayout : 'horizontal', 933 label : commonLang.width, 934 id : 'width', 935 setup : setupPopupParams, 936 commit : commitPopupParams 937 938 }, 939 { 940 type : 'text', 941 labelLayout : 'horizontal', 942 widths : [ '50%', '50%' ], 943 label : linkLang.popupLeft, 944 id : 'left', 945 setup : setupPopupParams, 946 commit : commitPopupParams 947 948 } 949 ] 950 }, 951 { 952 type : 'hbox', 953 children : 954 [ 955 { 956 type : 'text', 957 labelLayout : 'horizontal', 958 widths : [ '50%', '50%' ], 959 label : commonLang.height, 960 id : 'height', 961 setup : setupPopupParams, 962 commit : commitPopupParams 963 964 }, 965 { 966 type : 'text', 967 labelLayout : 'horizontal', 968 label : linkLang.popupTop, 969 widths : [ '50%', '50%' ], 970 id : 'top', 971 setup : setupPopupParams, 972 commit : commitPopupParams 973 974 } 975 ] 976 } 977 ] 978 } 979 ] 980 } 981 ] 982 }, 983 { 984 id : 'upload', 985 label : linkLang.upload, 986 title : linkLang.upload, 987 hidden : true, 988 filebrowser : 'uploadButton', 989 elements : 990 [ 991 { 992 type : 'file', 993 id : 'upload', 994 label : commonLang.upload, 995 style: 'height:40px', 996 size : 29 997 }, 998 { 999 type : 'fileButton', 1000 id : 'uploadButton', 1001 label : commonLang.uploadSubmit, 1002 filebrowser : 'info:url', 1003 'for' : [ 'upload', 'upload' ] 1004 } 1005 ] 1006 }, 1007 { 1008 id : 'advanced', 1009 label : linkLang.advanced, 1010 title : linkLang.advanced, 1011 elements : 1012 [ 1013 { 1014 type : 'vbox', 1015 padding : 1, 1016 children : 1017 [ 1018 { 1019 type : 'hbox', 1020 widths : [ '45%', '35%', '20%' ], 1021 children : 1022 [ 1023 { 1024 type : 'text', 1025 id : 'advId', 1026 label : linkLang.id, 1027 setup : setupAdvParams, 1028 commit : commitAdvParams 1029 }, 1030 { 1031 type : 'select', 1032 id : 'advLangDir', 1033 label : linkLang.langDir, 1034 'default' : '', 1035 style : 'width:110px', 1036 items : 1037 [ 1038 [ commonLang.notSet, '' ], 1039 [ linkLang.langDirLTR, 'ltr' ], 1040 [ linkLang.langDirRTL, 'rtl' ] 1041 ], 1042 setup : setupAdvParams, 1043 commit : commitAdvParams 1044 }, 1045 { 1046 type : 'text', 1047 id : 'advAccessKey', 1048 width : '80px', 1049 label : linkLang.acccessKey, 1050 maxLength : 1, 1051 setup : setupAdvParams, 1052 commit : commitAdvParams 1053 1054 } 1055 ] 1056 }, 1057 { 1058 type : 'hbox', 1059 widths : [ '45%', '35%', '20%' ], 1060 children : 1061 [ 1062 { 1063 type : 'text', 1064 label : linkLang.name, 1065 id : 'advName', 1066 setup : setupAdvParams, 1067 commit : commitAdvParams 1068 1069 }, 1070 { 1071 type : 'text', 1072 label : linkLang.langCode, 1073 id : 'advLangCode', 1074 width : '110px', 1075 'default' : '', 1076 setup : setupAdvParams, 1077 commit : commitAdvParams 1078 1079 }, 1080 { 1081 type : 'text', 1082 label : linkLang.tabIndex, 1083 id : 'advTabIndex', 1084 width : '80px', 1085 maxLength : 5, 1086 setup : setupAdvParams, 1087 commit : commitAdvParams 1088 1089 } 1090 ] 1091 } 1092 ] 1093 }, 1094 { 1095 type : 'vbox', 1096 padding : 1, 1097 children : 1098 [ 1099 { 1100 type : 'hbox', 1101 widths : [ '45%', '55%' ], 1102 children : 1103 [ 1104 { 1105 type : 'text', 1106 label : linkLang.advisoryTitle, 1107 'default' : '', 1108 id : 'advTitle', 1109 setup : setupAdvParams, 1110 commit : commitAdvParams 1111 1112 }, 1113 { 1114 type : 'text', 1115 label : linkLang.advisoryContentType, 1116 'default' : '', 1117 id : 'advContentType', 1118 setup : setupAdvParams, 1119 commit : commitAdvParams 1120 1121 } 1122 ] 1123 }, 1124 { 1125 type : 'hbox', 1126 widths : [ '45%', '55%' ], 1127 children : 1128 [ 1129 { 1130 type : 'text', 1131 label : linkLang.cssClasses, 1132 'default' : '', 1133 id : 'advCSSClasses', 1134 setup : setupAdvParams, 1135 commit : commitAdvParams 1136 1137 }, 1138 { 1139 type : 'text', 1140 label : linkLang.charset, 1141 'default' : '', 1142 id : 'advCharset', 1143 setup : setupAdvParams, 1144 commit : commitAdvParams 1145 1146 } 1147 ] 1148 }, 1149 { 1150 type : 'hbox', 1151 widths : [ '45%', '55%' ], 1152 children : 1153 [ 1154 { 1155 type : 'text', 1156 label : linkLang.rel, 1157 'default' : '', 1158 id : 'advRel', 1159 setup : setupAdvParams, 1160 commit : commitAdvParams 1161 }, 1162 { 1163 type : 'text', 1164 label : linkLang.styles, 1165 'default' : '', 1166 id : 'advStyles', 1167 validate : CKEDITOR.dialog.validate.inlineStyle( editor.lang.common.invalidInlineStyle ), 1168 setup : setupAdvParams, 1169 commit : commitAdvParams 1170 } 1171 ] 1172 } 1173 ] 1174 } 1175 ] 1176 } 1177 ], 1178 onShow : function() 1179 { 1180 var editor = this.getParentEditor(), 1181 selection = editor.getSelection(), 1182 element = null; 1183 1184 // Fill in all the relevant fields if there's already one link selected. 1185 if ( ( element = plugin.getSelectedLink( editor ) ) && element.hasAttribute( 'href' ) ) 1186 selection.selectElement( element ); 1187 else 1188 element = null; 1189 1190 this.setupContent( parseLink.apply( this, [ editor, element ] ) ); 1191 }, 1192 onOk : function() 1193 { 1194 var attributes = {}, 1195 removeAttributes = [], 1196 data = {}, 1197 me = this, 1198 editor = this.getParentEditor(); 1199 1200 this.commitContent( data ); 1201 1202 // Compose the URL. 1203 switch ( data.type || 'url' ) 1204 { 1205 case 'url': 1206 var protocol = ( data.url && data.url.protocol != undefined ) ? data.url.protocol : 'http://', 1207 url = ( data.url && CKEDITOR.tools.trim( data.url.url ) ) || ''; 1208 attributes[ 'data-cke-saved-href' ] = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url; 1209 break; 1210 case 'anchor': 1211 var name = ( data.anchor && data.anchor.name ), 1212 id = ( data.anchor && data.anchor.id ); 1213 attributes[ 'data-cke-saved-href' ] = '#' + ( name || id || '' ); 1214 break; 1215 case 'email': 1216 1217 var linkHref, 1218 email = data.email, 1219 address = email.address; 1220 1221 switch( emailProtection ) 1222 { 1223 case '' : 1224 case 'encode' : 1225 { 1226 var subject = encodeURIComponent( email.subject || '' ), 1227 body = encodeURIComponent( email.body || '' ); 1228 1229 // Build the e-mail parameters first. 1230 var argList = []; 1231 subject && argList.push( 'subject=' + subject ); 1232 body && argList.push( 'body=' + body ); 1233 argList = argList.length ? '?' + argList.join( '&' ) : ''; 1234 1235 if ( emailProtection == 'encode' ) 1236 { 1237 linkHref = [ 'javascript:void(location.href=\'mailto:\'+', 1238 protectEmailAddressAsEncodedString( address ) ]; 1239 // parameters are optional. 1240 argList && linkHref.push( '+\'', escapeSingleQuote( argList ), '\'' ); 1241 1242 linkHref.push( ')' ); 1243 } 1244 else 1245 linkHref = [ 'mailto:', address, argList ]; 1246 1247 break; 1248 } 1249 default : 1250 { 1251 // Separating name and domain. 1252 var nameAndDomain = address.split( '@', 2 ); 1253 email.name = nameAndDomain[ 0 ]; 1254 email.domain = nameAndDomain[ 1 ]; 1255 1256 linkHref = [ 'javascript:', protectEmailLinkAsFunction( email ) ]; 1257 } 1258 } 1259 1260 attributes[ 'data-cke-saved-href' ] = linkHref.join( '' ); 1261 break; 1262 } 1263 1264 // Popups and target. 1265 if ( data.target ) 1266 { 1267 if ( data.target.type == 'popup' ) 1268 { 1269 var onclickList = [ 'window.open(this.href, \'', 1270 data.target.name || '', '\', \'' ]; 1271 var featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen', 1272 'scrollbars', 'dependent' ]; 1273 var featureLength = featureList.length; 1274 var addFeature = function( featureName ) 1275 { 1276 if ( data.target[ featureName ] ) 1277 featureList.push( featureName + '=' + data.target[ featureName ] ); 1278 }; 1279 1280 for ( var i = 0 ; i < featureLength ; i++ ) 1281 featureList[i] = featureList[i] + ( data.target[ featureList[i] ] ? '=yes' : '=no' ) ; 1282 addFeature( 'width' ); 1283 addFeature( 'left' ); 1284 addFeature( 'height' ); 1285 addFeature( 'top' ); 1286 1287 onclickList.push( featureList.join( ',' ), '\'); return false;' ); 1288 attributes[ 'data-cke-pa-onclick' ] = onclickList.join( '' ); 1289 1290 // Add the "target" attribute. (#5074) 1291 removeAttributes.push( 'target' ); 1292 } 1293 else 1294 { 1295 if ( data.target.type != 'notSet' && data.target.name ) 1296 attributes.target = data.target.name; 1297 else 1298 removeAttributes.push( 'target' ); 1299 1300 removeAttributes.push( 'data-cke-pa-onclick', 'onclick' ); 1301 } 1302 } 1303 1304 // Advanced attributes. 1305 if ( data.adv ) 1306 { 1307 var advAttr = function( inputName, attrName ) 1308 { 1309 var value = data.adv[ inputName ]; 1310 if ( value ) 1311 attributes[attrName] = value; 1312 else 1313 removeAttributes.push( attrName ); 1314 }; 1315 1316 advAttr( 'advId', 'id' ); 1317 advAttr( 'advLangDir', 'dir' ); 1318 advAttr( 'advAccessKey', 'accessKey' ); 1319 1320 if ( data.adv[ 'advName' ] ) 1321 attributes[ 'name' ] = attributes[ 'data-cke-saved-name' ] = data.adv[ 'advName' ]; 1322 else 1323 removeAttributes = removeAttributes.concat( [ 'data-cke-saved-name', 'name' ] ); 1324 1325 advAttr( 'advLangCode', 'lang' ); 1326 advAttr( 'advTabIndex', 'tabindex' ); 1327 advAttr( 'advTitle', 'title' ); 1328 advAttr( 'advContentType', 'type' ); 1329 advAttr( 'advCSSClasses', 'class' ); 1330 advAttr( 'advCharset', 'charset' ); 1331 advAttr( 'advStyles', 'style' ); 1332 advAttr( 'advRel', 'rel' ); 1333 } 1334 1335 1336 var selection = editor.getSelection(); 1337 1338 // Browser need the "href" fro copy/paste link to work. (#6641) 1339 attributes.href = attributes[ 'data-cke-saved-href' ]; 1340 1341 if ( !this._.selectedElement ) 1342 { 1343 // Create element if current selection is collapsed. 1344 var ranges = selection.getRanges( true ); 1345 if ( ranges.length == 1 && ranges[0].collapsed ) 1346 { 1347 // Short mailto link text view (#5736). 1348 var text = new CKEDITOR.dom.text( data.type == 'email' ? 1349 data.email.address : attributes[ 'data-cke-saved-href' ], editor.document ); 1350 ranges[0].insertNode( text ); 1351 ranges[0].selectNodeContents( text ); 1352 selection.selectRanges( ranges ); 1353 } 1354 1355 // Apply style. 1356 var style = new CKEDITOR.style( { element : 'a', attributes : attributes } ); 1357 style.type = CKEDITOR.STYLE_INLINE; // need to override... dunno why. 1358 style.apply( editor.document ); 1359 } 1360 else 1361 { 1362 // We're only editing an existing link, so just overwrite the attributes. 1363 var element = this._.selectedElement, 1364 href = element.data( 'cke-saved-href' ), 1365 textView = element.getHtml(); 1366 1367 element.setAttributes( attributes ); 1368 element.removeAttributes( removeAttributes ); 1369 1370 if ( data.adv && data.adv.advName && CKEDITOR.plugins.link.synAnchorSelector ) 1371 element.addClass( element.getChildCount() ? 'cke_anchor' : 'cke_anchor_empty' ); 1372 1373 // Update text view when user changes protocol (#4612). 1374 if ( href == textView || data.type == 'email' && textView.indexOf( '@' ) != -1 ) 1375 { 1376 // Short mailto link text view (#5736). 1377 element.setHtml( data.type == 'email' ? 1378 data.email.address : attributes[ 'data-cke-saved-href' ] ); 1379 } 1380 1381 selection.selectElement( element ); 1382 delete this._.selectedElement; 1383 } 1384 }, 1385 onLoad : function() 1386 { 1387 if ( !editor.config.linkShowAdvancedTab ) 1388 this.hidePage( 'advanced' ); //Hide Advanded tab. 1389 1390 if ( !editor.config.linkShowTargetTab ) 1391 this.hidePage( 'target' ); //Hide Target tab. 1392 1393 }, 1394 // Inital focus on 'url' field if link is of type URL. 1395 onFocus : function() 1396 { 1397 var linkType = this.getContentElement( 'info', 'linkType' ), 1398 urlField; 1399 if ( linkType && linkType.getValue() == 'url' ) 1400 { 1401 urlField = this.getContentElement( 'info', 'url' ); 1402 urlField.select(); 1403 } 1404 } 1405 }; 1406 }); 1407 1408 /** 1409 * The e-mail address anti-spam protection option. The protection will be 1410 * applied when creating or modifying e-mail links through the editor interface.<br> 1411 * Two methods of protection can be choosed: 1412 * <ol> <li>The e-mail parts (name, domain and any other query string) are 1413 * assembled into a function call pattern. Such function must be 1414 * provided by the developer in the pages that will use the contents. 1415 * <li>Only the e-mail address is obfuscated into a special string that 1416 * has no meaning for humans or spam bots, but which is properly 1417 * rendered and accepted by the browser.</li></ol> 1418 * Both approaches require JavaScript to be enabled. 1419 * @name CKEDITOR.config.emailProtection 1420 * @since 3.1 1421 * @type String 1422 * @default '' (empty string = disabled) 1423 * @example 1424 * // href="mailto:tester@ckeditor.com?subject=subject&body=body" 1425 * config.emailProtection = ''; 1426 * @example 1427 * // href="<a href=\"javascript:void(location.href=\'mailto:\'+String.fromCharCode(116,101,115,116,101,114,64,99,107,101,100,105,116,111,114,46,99,111,109)+\'?subject=subject&body=body\')\">e-mail</a>" 1428 * config.emailProtection = 'encode'; 1429 * @example 1430 * // href="javascript:mt('tester','ckeditor.com','subject','body')" 1431 * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)'; 1432 */ 1433