2 * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
9 CKEDITOR
.dialog
.add( 'link', function( editor
) {
10 var plugin
= CKEDITOR
.plugins
.link
;
12 // Handles the event when the "Target" selection box is changed.
13 var targetChanged = function() {
14 var dialog
= this.getDialog(),
15 popupFeatures
= dialog
.getContentElement( 'target', 'popupFeatures' ),
16 targetName
= dialog
.getContentElement( 'target', 'linkTargetName' ),
17 value
= this.getValue();
19 if ( !popupFeatures
|| !targetName
)
22 popupFeatures
= popupFeatures
.getElement();
24 targetName
.setValue( '' );
28 targetName
.setLabel( editor
.lang
.link
.targetFrameName
);
29 targetName
.getElement().show();
33 targetName
.setLabel( editor
.lang
.link
.targetPopupName
);
34 targetName
.getElement().show();
37 targetName
.setValue( value
);
38 targetName
.getElement().hide();
44 // Handles the event when the "Type" selection box is changed.
45 var linkTypeChanged = function() {
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
;
52 if ( typeValue
== 'url' ) {
53 if ( editor
.config
.linkShowTargetTab
)
54 dialog
.showPage( 'target' );
55 if ( !uploadInitiallyHidden
)
56 dialog
.showPage( 'upload' );
58 dialog
.hidePage( 'target' );
59 if ( !uploadInitiallyHidden
)
60 dialog
.hidePage( 'upload' );
63 for ( var i
= 0; i
< partIds
.length
; i
++ ) {
64 var element
= dialog
.getContentElement( 'info', partIds
[ i
] );
68 element
= element
.getElement().getParent().getParent();
69 if ( partIds
[ i
] == typeValue
+ 'Options' )
78 var setupParams = function( page
, data
) {
80 this.setValue( data
[ page
][ this.id
] || '' );
83 var setupPopupParams = function( data
) {
84 return setupParams
.call( this, 'target', data
);
87 var setupAdvParams = function( data
) {
88 return setupParams
.call( this, 'advanced', data
);
91 var commitParams = function( page
, data
) {
95 data
[ page
][ this.id
] = this.getValue() || '';
98 var commitPopupParams = function( data
) {
99 return commitParams
.call( this, 'target', data
);
102 var commitAdvParams = function( data
) {
103 return commitParams
.call( this, 'advanced', data
);
106 var commonLang
= editor
.lang
.common
,
107 linkLang
= editor
.lang
.link
,
111 title: linkLang
.title
,
116 label: linkLang
.info
,
117 title: linkLang
.info
,
121 label: linkLang
.type
,
124 [ linkLang
.toUrl
, 'url' ],
125 [ linkLang
.toAnchor
, 'anchor' ],
126 [ linkLang
.toEmail
, 'email' ]
128 onChange: linkTypeChanged
,
129 setup: function( data
) {
130 this.setValue( data
.type
|| 'url' );
132 commit: function( data
) {
133 data
.type
= this.getValue();
141 widths: [ '25%', '75%' ],
145 label: commonLang
.protocol
,
146 'default': 'http://',
148 // Force 'ltr' for protocol names in BIDI. (#5433)
149 [ 'http://\u200E', 'http://' ],
150 [ 'https://\u200E', 'https://' ],
151 [ 'ftp://\u200E', 'ftp://' ],
152 [ 'news://\u200E', 'news://' ],
153 [ linkLang
.other
, '' ]
155 setup: function( data
) {
157 this.setValue( data
.url
.protocol
|| '' );
159 commit: function( data
) {
163 data
.url
.protocol
= this.getValue();
169 label: commonLang
.url
,
172 this.allowOnChange
= true;
174 onKeyUp: function() {
175 this.allowOnChange
= false;
176 var protocolCmb
= this.getDialog().getContentElement( 'info', 'protocol' ),
177 url
= this.getValue(),
178 urlOnChangeProtocol
= /^(http|https|ftp|news):\/\/(?=.)/i,
179 urlOnChangeTestOther
= /^((javascript:)|[#\/\.\?])/i;
181 var protocol
= urlOnChangeProtocol
.exec( url
);
183 this.setValue( url
.substr( protocol
[ 0 ].length
) );
184 protocolCmb
.setValue( protocol
[ 0 ].toLowerCase() );
185 } else if ( urlOnChangeTestOther
.test( url
) ) {
186 protocolCmb
.setValue( '' );
189 this.allowOnChange
= true;
191 onChange: function() {
192 if ( this.allowOnChange
) // Dont't call on dialog load.
195 validate: function() {
196 var dialog
= this.getDialog();
198 if ( dialog
.getContentElement( 'info', 'linkType' ) && dialog
.getValueOf( 'info', 'linkType' ) != 'url' )
201 if ( !editor
.config
.linkJavaScriptLinksAllowed
&& ( /javascript\:/ ).test( this.getValue() ) ) {
202 alert( commonLang
.invalidValue
); // jshint ignore:line
206 if ( this.getDialog().fakeObj
) // Edit Anchor.
209 var func
= CKEDITOR
.dialog
.validate
.notEmpty( linkLang
.noUrl
);
210 return func
.apply( this );
212 setup: function( data
) {
213 this.allowOnChange
= false;
215 this.setValue( data
.url
.url
);
216 this.allowOnChange
= true;
219 commit: function( data
) {
220 // IE will not trigger the onChange event if the mouse has been used
221 // to carry all the operations #4724
227 data
.url
.url
= this.getValue();
228 this.allowOnChange
= false;
232 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
233 this.getElement().show();
240 filebrowser: 'info:url',
241 label: commonLang
.browseServer
252 id: 'selectAnchorText',
253 label: linkLang
.selectAnchor
,
255 anchors
= plugin
.getEditorAnchors( editor
);
257 this.getElement()[ anchors
&& anchors
.length
? 'show' : 'hide' ]();
266 label: linkLang
.anchorName
,
267 style: 'width: 100%;',
271 setup: function( data
) {
276 for ( var i
= 0; i
< anchors
.length
; i
++ ) {
277 if ( anchors
[ i
].name
)
278 this.add( anchors
[ i
].name
);
283 this.setValue( data
.anchor
.name
);
285 var linkType
= this.getDialog().getContentElement( 'info', 'linkType' );
286 if ( linkType
&& linkType
.getValue() == 'email' )
289 commit: function( data
) {
293 data
.anchor
.name
= this.getValue();
300 label: linkLang
.anchorId
,
301 style: 'width: 100%;',
305 setup: function( data
) {
310 for ( var i
= 0; i
< anchors
.length
; i
++ ) {
311 if ( anchors
[ i
].id
)
312 this.add( anchors
[ i
].id
);
317 this.setValue( data
.anchor
.id
);
319 commit: function( data
) {
323 data
.anchor
.id
= this.getValue();
327 this.getElement()[ anchors
&& anchors
.length
? 'show' : 'hide' ]();
334 style: 'text-align: center;',
335 html: '<div role="note" tabIndex="-1">' + CKEDITOR
.tools
.htmlEncode( linkLang
.noAnchors
) + '</div>',
336 // Focus the first element defined in above html.
339 this.getElement()[ anchors
&& anchors
.length
? 'hide' : 'show' ]();
343 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
344 this.getElement().hide();
354 label: linkLang
.emailAddress
,
356 validate: function() {
357 var dialog
= this.getDialog();
359 if ( !dialog
.getContentElement( 'info', 'linkType' ) || dialog
.getValueOf( 'info', 'linkType' ) != 'email' )
362 var func
= CKEDITOR
.dialog
.validate
.notEmpty( linkLang
.noEmail
);
363 return func
.apply( this );
365 setup: function( data
) {
367 this.setValue( data
.email
.address
);
369 var linkType
= this.getDialog().getContentElement( 'info', 'linkType' );
370 if ( linkType
&& linkType
.getValue() == 'email' )
373 commit: function( data
) {
377 data
.email
.address
= this.getValue();
383 label: linkLang
.emailSubject
,
384 setup: function( data
) {
386 this.setValue( data
.email
.subject
);
388 commit: function( data
) {
392 data
.email
.subject
= this.getValue();
398 label: linkLang
.emailBody
,
401 setup: function( data
) {
403 this.setValue( data
.email
.body
);
405 commit: function( data
) {
409 data
.email
.body
= this.getValue();
413 if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
414 this.getElement().hide();
420 requiredContent: 'a[target]', // This is not fully correct, because some target option requires JS.
421 label: linkLang
.target
,
422 title: linkLang
.target
,
425 widths: [ '50%', '50%' ],
428 id: 'linkTargetType',
429 label: commonLang
.target
,
431 style: 'width : 100%;',
433 [ commonLang
.notSet
, 'notSet' ],
434 [ linkLang
.targetFrame
, 'frame' ],
435 [ linkLang
.targetPopup
, 'popup' ],
436 [ commonLang
.targetNew
, '_blank' ],
437 [ commonLang
.targetTop
, '_top' ],
438 [ commonLang
.targetSelf
, '_self' ],
439 [ commonLang
.targetParent
, '_parent' ]
441 onChange: targetChanged
,
442 setup: function( data
) {
444 this.setValue( data
.target
.type
|| 'notSet' );
445 targetChanged
.call( this );
447 commit: function( data
) {
451 data
.target
.type
= this.getValue();
456 id: 'linkTargetName',
457 label: linkLang
.targetFrameName
,
459 setup: function( data
) {
461 this.setValue( data
.target
.name
);
463 commit: function( data
) {
467 data
.target
.name
= this.getValue().replace( /([^\x00-\x7F]|\s)/gi, '' );
479 label: linkLang
.popupFeatures
,
485 label: linkLang
.popupResizable
,
486 setup: setupPopupParams
,
487 commit: commitPopupParams
492 label: linkLang
.popupStatusBar
,
493 setup: setupPopupParams
,
494 commit: commitPopupParams
503 label: linkLang
.popupLocationBar
,
504 setup: setupPopupParams
,
505 commit: commitPopupParams
511 label: linkLang
.popupToolbar
,
512 setup: setupPopupParams
,
513 commit: commitPopupParams
522 label: linkLang
.popupMenuBar
,
523 setup: setupPopupParams
,
524 commit: commitPopupParams
530 label: linkLang
.popupFullScreen
,
531 setup: setupPopupParams
,
532 commit: commitPopupParams
541 label: linkLang
.popupScrollBars
,
542 setup: setupPopupParams
,
543 commit: commitPopupParams
549 label: linkLang
.popupDependent
,
550 setup: setupPopupParams
,
551 commit: commitPopupParams
559 widths: [ '50%', '50%' ],
560 labelLayout: 'horizontal',
561 label: commonLang
.width
,
563 setup: setupPopupParams
,
564 commit: commitPopupParams
569 labelLayout: 'horizontal',
570 widths: [ '50%', '50%' ],
571 label: linkLang
.popupLeft
,
573 setup: setupPopupParams
,
574 commit: commitPopupParams
582 labelLayout: 'horizontal',
583 widths: [ '50%', '50%' ],
584 label: commonLang
.height
,
586 setup: setupPopupParams
,
587 commit: commitPopupParams
592 labelLayout: 'horizontal',
593 label: linkLang
.popupTop
,
594 widths: [ '50%', '50%' ],
596 setup: setupPopupParams
,
597 commit: commitPopupParams
606 label: linkLang
.upload
,
607 title: linkLang
.upload
,
609 filebrowser: 'uploadButton',
613 label: commonLang
.upload
,
614 style: 'height:40px',
620 label: commonLang
.uploadSubmit
,
621 filebrowser: 'info:url',
622 'for': [ 'upload', 'upload' ]
627 label: linkLang
.advanced
,
628 title: linkLang
.advanced
,
634 widths: [ '45%', '35%', '20%' ],
638 requiredContent: 'a[id]',
640 setup: setupAdvParams
,
641 commit: commitAdvParams
646 requiredContent: 'a[dir]',
647 label: linkLang
.langDir
,
649 style: 'width:110px',
651 [ commonLang
.notSet
, '' ],
652 [ linkLang
.langDirLTR
, 'ltr' ],
653 [ linkLang
.langDirRTL
, 'rtl' ]
655 setup: setupAdvParams
,
656 commit: commitAdvParams
661 requiredContent: 'a[accesskey]',
663 label: linkLang
.acccessKey
,
665 setup: setupAdvParams
,
666 commit: commitAdvParams
672 widths: [ '45%', '35%', '20%' ],
675 label: linkLang
.name
,
677 requiredContent: 'a[name]',
678 setup: setupAdvParams
,
679 commit: commitAdvParams
684 label: linkLang
.langCode
,
686 requiredContent: 'a[lang]',
689 setup: setupAdvParams
,
690 commit: commitAdvParams
695 label: linkLang
.tabIndex
,
697 requiredContent: 'a[tabindex]',
700 setup: setupAdvParams
,
701 commit: commitAdvParams
711 widths: [ '45%', '55%' ],
714 label: linkLang
.advisoryTitle
,
715 requiredContent: 'a[title]',
718 setup: setupAdvParams
,
719 commit: commitAdvParams
724 label: linkLang
.advisoryContentType
,
725 requiredContent: 'a[type]',
727 id: 'advContentType',
728 setup: setupAdvParams
,
729 commit: commitAdvParams
735 widths: [ '45%', '55%' ],
738 label: linkLang
.cssClasses
,
739 requiredContent: 'a(cke-xyz)', // Random text like 'xyz' will check if all are allowed.
742 setup: setupAdvParams
,
743 commit: commitAdvParams
748 label: linkLang
.charset
,
749 requiredContent: 'a[charset]',
752 setup: setupAdvParams
,
753 commit: commitAdvParams
759 widths: [ '45%', '55%' ],
763 requiredContent: 'a[rel]',
766 setup: setupAdvParams
,
767 commit: commitAdvParams
771 label: linkLang
.styles
,
772 requiredContent: 'a{cke-xyz}', // Random text like 'xyz' will check if all are allowed.
775 validate: CKEDITOR
.dialog
.validate
.inlineStyle( editor
.lang
.common
.invalidInlineStyle
),
776 setup: setupAdvParams
,
777 commit: commitAdvParams
783 var editor
= this.getParentEditor(),
784 selection
= editor
.getSelection(),
787 // Fill in all the relevant fields if there's already one link selected.
788 if ( ( element
= plugin
.getSelectedLink( editor
) ) && element
.hasAttribute( 'href' ) ) {
789 // Don't change selection if some element is already selected.
790 // For example - don't destroy fake selection.
791 if ( !selection
.getSelectedElement() )
792 selection
.selectElement( element
);
797 var data
= plugin
.parseLinkAttributes( editor
, element
);
799 // Record down the selected element in the dialog.
800 this._
.selectedElement
= element
;
802 this.setupContent( data
);
807 // Collect data from fields.
808 this.commitContent( data
);
810 var selection
= editor
.getSelection(),
811 attributes
= plugin
.getLinkAttributes( editor
, data
);
813 if ( !this._
.selectedElement
) {
814 var range
= selection
.getRanges()[ 0 ];
816 // Use link URL as text with a collapsed cursor.
817 if ( range
.collapsed
) {
818 // Short mailto link text view (#5736).
819 var text
= new CKEDITOR
.dom
.text( data
.type
== 'email' ?
820 data
.email
.address : attributes
.set[ 'data-cke-saved-href' ], editor
.document
);
821 range
.insertNode( text
);
822 range
.selectNodeContents( text
);
826 var style
= new CKEDITOR
.style( {
828 attributes: attributes
.set
831 style
.type
= CKEDITOR
.STYLE_INLINE
; // need to override... dunno why.
832 style
.applyToRange( range
, editor
);
835 // We're only editing an existing link, so just overwrite the attributes.
836 var element
= this._
.selectedElement
,
837 href
= element
.data( 'cke-saved-href' ),
838 textView
= element
.getHtml();
840 element
.setAttributes( attributes
.set );
841 element
.removeAttributes( attributes
.removed
);
843 // Update text view when user changes protocol (#4612).
844 if ( href
== textView
|| data
.type
== 'email' && textView
.indexOf( '@' ) != -1 ) {
845 // Short mailto link text view (#5736).
846 element
.setHtml( data
.type
== 'email' ?
847 data
.email
.address : attributes
.set[ 'data-cke-saved-href' ] );
849 // We changed the content, so need to select it again.
850 selection
.selectElement( element
);
853 delete this._
.selectedElement
;
857 if ( !editor
.config
.linkShowAdvancedTab
)
858 this.hidePage( 'advanced' ); //Hide Advanded tab.
860 if ( !editor
.config
.linkShowTargetTab
)
861 this.hidePage( 'target' ); //Hide Target tab.
863 // Inital focus on 'url' field if link is of type URL.
864 onFocus: function() {
865 var linkType
= this.getContentElement( 'info', 'linkType' ),
868 if ( linkType
&& linkType
.getValue() == 'url' ) {
869 urlField
= this.getContentElement( 'info', 'url' );
876 // jscs:disable maximumLineLength
878 * The e-mail address anti-spam protection option. The protection will be
879 * applied when creating or modifying e-mail links through the editor interface.
881 * Two methods of protection can be chosen:
883 * 1. The e-mail parts (name, domain, and any other query string) are
884 * assembled into a function call pattern. Such function must be
885 * provided by the developer in the pages that will use the contents.
886 * 2. Only the e-mail address is obfuscated into a special string that
887 * has no meaning for humans or spam bots, but which is properly
888 * rendered and accepted by the browser.
890 * Both approaches require JavaScript to be enabled.
892 * // href="mailto:tester@ckeditor.com?subject=subject&body=body"
893 * config.emailProtection = '';
895 * // 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>"
896 * config.emailProtection = 'encode';
898 * // href="javascript:mt('tester','ckeditor.com','subject','body')"
899 * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)';
902 * @cfg {String} [emailProtection='' (empty string = disabled)]
903 * @member CKEDITOR.config