PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/files/ckeditor/4.3.0/plugins/link/dialogs/link.js

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