plugin.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. 'use strict';
  6. ( function() {
  7. CKEDITOR.plugins.add( 'link', {
  8. requires: 'dialog,fakeobjects',
  9. // jscs:disable maximumLineLength
  10. lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
  11. // jscs:enable maximumLineLength
  12. icons: 'anchor,anchor-rtl,link,unlink', // %REMOVE_LINE_CORE%
  13. hidpi: true, // %REMOVE_LINE_CORE%
  14. onLoad: function() {
  15. // Add the CSS styles for anchor placeholders.
  16. var iconPath = CKEDITOR.getUrl( this.path + 'images' + ( CKEDITOR.env.hidpi ? '/hidpi' : '' ) + '/anchor.png' ),
  17. baseStyle = 'background:url(' + iconPath + ') no-repeat %1 center;border:1px dotted #00f;background-size:16px;';
  18. var template = '.%2 a.cke_anchor,' +
  19. '.%2 a.cke_anchor_empty' +
  20. ',.cke_editable.%2 a[name]' +
  21. ',.cke_editable.%2 a[data-cke-saved-name]' +
  22. '{' +
  23. baseStyle +
  24. 'padding-%1:18px;' +
  25. // Show the arrow cursor for the anchor image (FF at least).
  26. 'cursor:auto;' +
  27. '}' +
  28. '.%2 img.cke_anchor' +
  29. '{' +
  30. baseStyle +
  31. 'width:16px;' +
  32. 'min-height:15px;' +
  33. // The default line-height on IE.
  34. 'height:1.15em;' +
  35. // Opera works better with "middle" (even if not perfect)
  36. 'vertical-align:text-bottom;' +
  37. '}';
  38. // Styles with contents direction awareness.
  39. function cssWithDir( dir ) {
  40. return template.replace( /%1/g, dir == 'rtl' ? 'right' : 'left' ).replace( /%2/g, 'cke_contents_' + dir );
  41. }
  42. CKEDITOR.addCss( cssWithDir( 'ltr' ) + cssWithDir( 'rtl' ) );
  43. },
  44. init: function( editor ) {
  45. var allowed = 'a[!href]',
  46. required = 'a[href]';
  47. if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'advanced' ) )
  48. allowed = allowed.replace( ']', ',accesskey,charset,dir,id,lang,name,rel,tabindex,title,type,target]{*}(*)' );
  49. if ( CKEDITOR.dialog.isTabEnabled( editor, 'link', 'target' ) )
  50. allowed = allowed.replace( ']', ',target,onclick]' );
  51. // Add the link and unlink buttons.
  52. editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link', {
  53. allowedContent: allowed,
  54. requiredContent: required
  55. } ) );
  56. editor.addCommand( 'anchor', new CKEDITOR.dialogCommand( 'anchor', {
  57. allowedContent: 'a[!name,id]',
  58. requiredContent: 'a[name]'
  59. } ) );
  60. editor.addCommand( 'unlink', new CKEDITOR.unlinkCommand() );
  61. editor.addCommand( 'removeAnchor', new CKEDITOR.removeAnchorCommand() );
  62. editor.setKeystroke( CKEDITOR.CTRL + 76 /*L*/, 'link' );
  63. if ( editor.ui.addButton ) {
  64. editor.ui.addButton( 'Link', {
  65. label: editor.lang.link.toolbar,
  66. command: 'link',
  67. toolbar: 'links,10'
  68. } );
  69. editor.ui.addButton( 'Unlink', {
  70. label: editor.lang.link.unlink,
  71. command: 'unlink',
  72. toolbar: 'links,20'
  73. } );
  74. editor.ui.addButton( 'Anchor', {
  75. label: editor.lang.link.anchor.toolbar,
  76. command: 'anchor',
  77. toolbar: 'links,30'
  78. } );
  79. }
  80. CKEDITOR.dialog.add( 'link', this.path + 'dialogs/link.js' );
  81. CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' );
  82. editor.on( 'doubleclick', function( evt ) {
  83. var element = CKEDITOR.plugins.link.getSelectedLink( editor ) || evt.data.element;
  84. if ( !element.isReadOnly() ) {
  85. if ( element.is( 'a' ) ) {
  86. evt.data.dialog = ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) ? 'anchor' : 'link';
  87. // Pass the link to be selected along with event data.
  88. evt.data.link = element;
  89. } else if ( CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element ) ) {
  90. evt.data.dialog = 'anchor';
  91. }
  92. }
  93. }, null, null, 0 );
  94. // If event was cancelled, link passed in event data will not be selected.
  95. editor.on( 'doubleclick', function( evt ) {
  96. // Make sure both links and anchors are selected (#11822).
  97. if ( evt.data.dialog in { link: 1, anchor: 1 } && evt.data.link )
  98. editor.getSelection().selectElement( evt.data.link );
  99. }, null, null, 20 );
  100. // If the "menu" plugin is loaded, register the menu items.
  101. if ( editor.addMenuItems ) {
  102. editor.addMenuItems( {
  103. anchor: {
  104. label: editor.lang.link.anchor.menu,
  105. command: 'anchor',
  106. group: 'anchor',
  107. order: 1
  108. },
  109. removeAnchor: {
  110. label: editor.lang.link.anchor.remove,
  111. command: 'removeAnchor',
  112. group: 'anchor',
  113. order: 5
  114. },
  115. link: {
  116. label: editor.lang.link.menu,
  117. command: 'link',
  118. group: 'link',
  119. order: 1
  120. },
  121. unlink: {
  122. label: editor.lang.link.unlink,
  123. command: 'unlink',
  124. group: 'link',
  125. order: 5
  126. }
  127. } );
  128. }
  129. // If the "contextmenu" plugin is loaded, register the listeners.
  130. if ( editor.contextMenu ) {
  131. editor.contextMenu.addListener( function( element ) {
  132. if ( !element || element.isReadOnly() )
  133. return null;
  134. var anchor = CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element );
  135. if ( !anchor && !( anchor = CKEDITOR.plugins.link.getSelectedLink( editor ) ) )
  136. return null;
  137. var menu = {};
  138. if ( anchor.getAttribute( 'href' ) && anchor.getChildCount() )
  139. menu = { link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF };
  140. if ( anchor && anchor.hasAttribute( 'name' ) )
  141. menu.anchor = menu.removeAnchor = CKEDITOR.TRISTATE_OFF;
  142. return menu;
  143. } );
  144. }
  145. this.compiledProtectionFunction = getCompiledProtectionFunction( editor );
  146. },
  147. afterInit: function( editor ) {
  148. // Empty anchors upcasting to fake objects.
  149. editor.dataProcessor.dataFilter.addRules( {
  150. elements: {
  151. a: function( element ) {
  152. if ( !element.attributes.name )
  153. return null;
  154. if ( !element.children.length )
  155. return editor.createFakeParserElement( element, 'cke_anchor', 'anchor' );
  156. return null;
  157. }
  158. }
  159. } );
  160. var pathFilters = editor._.elementsPath && editor._.elementsPath.filters;
  161. if ( pathFilters ) {
  162. pathFilters.push( function( element, name ) {
  163. if ( name == 'a' ) {
  164. if ( CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element ) || ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) )
  165. return 'anchor';
  166. }
  167. } );
  168. }
  169. }
  170. } );
  171. // Loads the parameters in a selected link to the link dialog fields.
  172. var javascriptProtocolRegex = /^javascript:/,
  173. emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/,
  174. emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/,
  175. emailBodyRegex = /body=([^;?:@&=$,\/]*)/,
  176. anchorRegex = /^#(.*)$/,
  177. urlRegex = /^((?:http|https|ftp|news):\/\/)?(.*)$/,
  178. selectableTargets = /^(_(?:self|top|parent|blank))$/,
  179. encodedEmailLinkRegex = /^javascript:void\(location\.href='mailto:'\+String\.fromCharCode\(([^)]+)\)(?:\+'(.*)')?\)$/,
  180. functionCallProtectedEmailLinkRegex = /^javascript:([^(]+)\(([^)]+)\)$/,
  181. popupRegex = /\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/,
  182. popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi;
  183. var advAttrNames = {
  184. id: 'advId',
  185. dir: 'advLangDir',
  186. accessKey: 'advAccessKey',
  187. // 'data-cke-saved-name': 'advName',
  188. name: 'advName',
  189. lang: 'advLangCode',
  190. tabindex: 'advTabIndex',
  191. title: 'advTitle',
  192. type: 'advContentType',
  193. 'class': 'advCSSClasses',
  194. charset: 'advCharset',
  195. style: 'advStyles',
  196. rel: 'advRel'
  197. };
  198. function unescapeSingleQuote( str ) {
  199. return str.replace( /\\'/g, '\'' );
  200. }
  201. function escapeSingleQuote( str ) {
  202. return str.replace( /'/g, '\\$&' );
  203. }
  204. function protectEmailAddressAsEncodedString( address ) {
  205. var charCode,
  206. length = address.length,
  207. encodedChars = [];
  208. for ( var i = 0; i < length; i++ ) {
  209. charCode = address.charCodeAt( i );
  210. encodedChars.push( charCode );
  211. }
  212. return 'String.fromCharCode(' + encodedChars.join( ',' ) + ')';
  213. }
  214. function protectEmailLinkAsFunction( editor, email ) {
  215. var plugin = editor.plugins.link,
  216. name = plugin.compiledProtectionFunction.name,
  217. params = plugin.compiledProtectionFunction.params,
  218. paramName, paramValue, retval;
  219. retval = [ name, '(' ];
  220. for ( var i = 0; i < params.length; i++ ) {
  221. paramName = params[ i ].toLowerCase();
  222. paramValue = email[ paramName ];
  223. i > 0 && retval.push( ',' );
  224. retval.push( '\'', paramValue ? escapeSingleQuote( encodeURIComponent( email[ paramName ] ) ) : '', '\'' );
  225. }
  226. retval.push( ')' );
  227. return retval.join( '' );
  228. }
  229. function getCompiledProtectionFunction( editor ) {
  230. var emailProtection = editor.config.emailProtection || '',
  231. compiledProtectionFunction;
  232. // Compile the protection function pattern.
  233. if ( emailProtection && emailProtection != 'encode' ) {
  234. compiledProtectionFunction = {};
  235. emailProtection.replace( /^([^(]+)\(([^)]+)\)$/, function( match, funcName, params ) {
  236. compiledProtectionFunction.name = funcName;
  237. compiledProtectionFunction.params = [];
  238. params.replace( /[^,\s]+/g, function( param ) {
  239. compiledProtectionFunction.params.push( param );
  240. } );
  241. } );
  242. }
  243. return compiledProtectionFunction;
  244. }
  245. /**
  246. * Set of Link plugin helpers.
  247. *
  248. * @class
  249. * @singleton
  250. */
  251. CKEDITOR.plugins.link = {
  252. /**
  253. * Get the surrounding link element of the current selection.
  254. *
  255. * CKEDITOR.plugins.link.getSelectedLink( editor );
  256. *
  257. * // The following selections will all return the link element.
  258. *
  259. * <a href="#">li^nk</a>
  260. * <a href="#">[link]</a>
  261. * text[<a href="#">link]</a>
  262. * <a href="#">li[nk</a>]
  263. * [<b><a href="#">li]nk</a></b>]
  264. * [<a href="#"><b>li]nk</b></a>
  265. *
  266. * @since 3.2.1
  267. * @param {CKEDITOR.editor} editor
  268. */
  269. getSelectedLink: function( editor ) {
  270. var selection = editor.getSelection();
  271. var selectedElement = selection.getSelectedElement();
  272. if ( selectedElement && selectedElement.is( 'a' ) )
  273. return selectedElement;
  274. var range = selection.getRanges()[ 0 ];
  275. if ( range ) {
  276. range.shrink( CKEDITOR.SHRINK_TEXT );
  277. return editor.elementPath( range.getCommonAncestor() ).contains( 'a', 1 );
  278. }
  279. return null;
  280. },
  281. /**
  282. * Collects anchors available in the editor (i.e. used by the Link plugin).
  283. * Note that the scope of search is different for inline (the "global" document) and
  284. * classic (`iframe`-based) editors (the "inner" document).
  285. *
  286. * @since 4.3.3
  287. * @param {CKEDITOR.editor} editor
  288. * @returns {CKEDITOR.dom.element[]} An array of anchor elements.
  289. */
  290. getEditorAnchors: function( editor ) {
  291. var editable = editor.editable(),
  292. // The scope of search for anchors is the entire document for inline editors
  293. // and editor's editable for classic editor/divarea (#11359).
  294. scope = ( editable.isInline() && !editor.plugins.divarea ) ? editor.document : editable,
  295. links = scope.getElementsByTag( 'a' ),
  296. imgs = scope.getElementsByTag( 'img' ),
  297. anchors = [],
  298. i = 0,
  299. item;
  300. // Retrieve all anchors within the scope.
  301. while ( ( item = links.getItem( i++ ) ) ) {
  302. if ( item.data( 'cke-saved-name' ) || item.hasAttribute( 'name' ) ) {
  303. anchors.push( {
  304. name: item.data( 'cke-saved-name' ) || item.getAttribute( 'name' ),
  305. id: item.getAttribute( 'id' )
  306. } );
  307. }
  308. }
  309. // Retrieve all "fake anchors" within the scope.
  310. i = 0;
  311. while ( ( item = imgs.getItem( i++ ) ) ) {
  312. if ( ( item = this.tryRestoreFakeAnchor( editor, item ) ) ) {
  313. anchors.push( {
  314. name: item.getAttribute( 'name' ),
  315. id: item.getAttribute( 'id' )
  316. } );
  317. }
  318. }
  319. return anchors;
  320. },
  321. /**
  322. * Opera and WebKit do not make it possible to select empty anchors. Fake
  323. * elements must be used for them.
  324. *
  325. * @readonly
  326. * @deprecated 4.3.3 It is set to `true` in every browser.
  327. * @property {Boolean}
  328. */
  329. fakeAnchor: true,
  330. /**
  331. * For browsers that do not support CSS3 `a[name]:empty()`. Note that IE9 is included because of #7783.
  332. *
  333. * @readonly
  334. * @deprecated 4.3.3 It is set to `false` in every browser.
  335. * @property {Boolean} synAnchorSelector
  336. */
  337. /**
  338. * For browsers that have editing issues with an empty anchor.
  339. *
  340. * @readonly
  341. * @deprecated 4.3.3 It is set to `false` in every browser.
  342. * @property {Boolean} emptyAnchorFix
  343. */
  344. /**
  345. * Returns an element representing a real anchor restored from a fake anchor.
  346. *
  347. * @param {CKEDITOR.editor} editor
  348. * @param {CKEDITOR.dom.element} element
  349. * @returns {CKEDITOR.dom.element} Restored anchor element or nothing if the
  350. * passed element was not a fake anchor.
  351. */
  352. tryRestoreFakeAnchor: function( editor, element ) {
  353. if ( element && element.data( 'cke-real-element-type' ) && element.data( 'cke-real-element-type' ) == 'anchor' ) {
  354. var link = editor.restoreRealElement( element );
  355. if ( link.data( 'cke-saved-name' ) )
  356. return link;
  357. }
  358. },
  359. /**
  360. * Parses attributes of the link element and returns an object representing
  361. * the current state (data) of the link. This data format is accepted e.g. by
  362. * the Link dialog window and {@link #getLinkAttributes}.
  363. *
  364. * @since 4.4
  365. * @param {CKEDITOR.editor} editor
  366. * @param {CKEDITOR.dom.element} element
  367. * @returns {Object} An object of link data.
  368. */
  369. parseLinkAttributes: function( editor, element ) {
  370. var href = ( element && ( element.data( 'cke-saved-href' ) || element.getAttribute( 'href' ) ) ) || '',
  371. compiledProtectionFunction = editor.plugins.link.compiledProtectionFunction,
  372. emailProtection = editor.config.emailProtection,
  373. javascriptMatch, emailMatch, anchorMatch, urlMatch,
  374. retval = {};
  375. if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) ) {
  376. if ( emailProtection == 'encode' ) {
  377. href = href.replace( encodedEmailLinkRegex, function( match, protectedAddress, rest ) {
  378. return 'mailto:' +
  379. String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) +
  380. ( rest && unescapeSingleQuote( rest ) );
  381. } );
  382. }
  383. // Protected email link as function call.
  384. else if ( emailProtection ) {
  385. href.replace( functionCallProtectedEmailLinkRegex, function( match, funcName, funcArgs ) {
  386. if ( funcName == compiledProtectionFunction.name ) {
  387. retval.type = 'email';
  388. var email = retval.email = {};
  389. var paramRegex = /[^,\s]+/g,
  390. paramQuoteRegex = /(^')|('$)/g,
  391. paramsMatch = funcArgs.match( paramRegex ),
  392. paramsMatchLength = paramsMatch.length,
  393. paramName, paramVal;
  394. for ( var i = 0; i < paramsMatchLength; i++ ) {
  395. paramVal = decodeURIComponent( unescapeSingleQuote( paramsMatch[ i ].replace( paramQuoteRegex, '' ) ) );
  396. paramName = compiledProtectionFunction.params[ i ].toLowerCase();
  397. email[ paramName ] = paramVal;
  398. }
  399. email.address = [ email.name, email.domain ].join( '@' );
  400. }
  401. } );
  402. }
  403. }
  404. if ( !retval.type ) {
  405. if ( ( anchorMatch = href.match( anchorRegex ) ) ) {
  406. retval.type = 'anchor';
  407. retval.anchor = {};
  408. retval.anchor.name = retval.anchor.id = anchorMatch[ 1 ];
  409. }
  410. // Protected email link as encoded string.
  411. else if ( ( emailMatch = href.match( emailRegex ) ) ) {
  412. var subjectMatch = href.match( emailSubjectRegex ),
  413. bodyMatch = href.match( emailBodyRegex );
  414. retval.type = 'email';
  415. var email = ( retval.email = {} );
  416. email.address = emailMatch[ 1 ];
  417. subjectMatch && ( email.subject = decodeURIComponent( subjectMatch[ 1 ] ) );
  418. bodyMatch && ( email.body = decodeURIComponent( bodyMatch[ 1 ] ) );
  419. }
  420. // urlRegex matches empty strings, so need to check for href as well.
  421. else if ( href && ( urlMatch = href.match( urlRegex ) ) ) {
  422. retval.type = 'url';
  423. retval.url = {};
  424. retval.url.protocol = urlMatch[ 1 ];
  425. retval.url.url = urlMatch[ 2 ];
  426. }
  427. }
  428. // Load target and popup settings.
  429. if ( element ) {
  430. var target = element.getAttribute( 'target' );
  431. // IE BUG: target attribute is an empty string instead of null in IE if it's not set.
  432. if ( !target ) {
  433. var onclick = element.data( 'cke-pa-onclick' ) || element.getAttribute( 'onclick' ),
  434. onclickMatch = onclick && onclick.match( popupRegex );
  435. if ( onclickMatch ) {
  436. retval.target = {
  437. type: 'popup',
  438. name: onclickMatch[ 1 ]
  439. };
  440. var featureMatch;
  441. while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[ 2 ] ) ) ) {
  442. // Some values should remain numbers (#7300)
  443. if ( ( featureMatch[ 2 ] == 'yes' || featureMatch[ 2 ] == '1' ) && !( featureMatch[ 1 ] in { height: 1, width: 1, top: 1, left: 1 } ) )
  444. retval.target[ featureMatch[ 1 ] ] = true;
  445. else if ( isFinite( featureMatch[ 2 ] ) )
  446. retval.target[ featureMatch[ 1 ] ] = featureMatch[ 2 ];
  447. }
  448. }
  449. } else {
  450. retval.target = {
  451. type: target.match( selectableTargets ) ? target : 'frame',
  452. name: target
  453. };
  454. }
  455. var advanced = {};
  456. for ( var a in advAttrNames ) {
  457. var val = element.getAttribute( a );
  458. if ( val )
  459. advanced[ advAttrNames[ a ] ] = val;
  460. }
  461. var advName = element.data( 'cke-saved-name' ) || advanced.advName;
  462. if ( advName )
  463. advanced.advName = advName;
  464. if ( !CKEDITOR.tools.isEmpty( advanced ) )
  465. retval.advanced = advanced;
  466. }
  467. return retval;
  468. },
  469. /**
  470. * Converts link data into an object which consists of attributes to be set
  471. * (with their values) and an array of attributes to be removed. This method
  472. * can be used to synthesise or to update any link element with the given data.
  473. *
  474. * @since 4.4
  475. * @param {CKEDITOR.editor} editor
  476. * @param {Object} data Data in {@link #parseLinkAttributes} format.
  477. * @returns {Object} An object consisting of two keys, i.e.:
  478. *
  479. * {
  480. * // Attributes to be set.
  481. * set: {
  482. * href: 'http://foo.bar',
  483. * target: 'bang'
  484. * },
  485. * // Attributes to be removed.
  486. * removed: [
  487. * 'id', 'style'
  488. * ]
  489. * }
  490. *
  491. */
  492. getLinkAttributes: function( editor, data ) {
  493. var emailProtection = editor.config.emailProtection || '',
  494. set = {};
  495. // Compose the URL.
  496. switch ( data.type ) {
  497. case 'url':
  498. var protocol = ( data.url && data.url.protocol !== undefined ) ? data.url.protocol : 'http://',
  499. url = ( data.url && CKEDITOR.tools.trim( data.url.url ) ) || '';
  500. set[ 'data-cke-saved-href' ] = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url;
  501. break;
  502. case 'anchor':
  503. var name = ( data.anchor && data.anchor.name ),
  504. id = ( data.anchor && data.anchor.id );
  505. set[ 'data-cke-saved-href' ] = '#' + ( name || id || '' );
  506. break;
  507. case 'email':
  508. var email = data.email,
  509. address = email.address,
  510. linkHref;
  511. switch ( emailProtection ) {
  512. case '':
  513. case 'encode':
  514. var subject = encodeURIComponent( email.subject || '' ),
  515. body = encodeURIComponent( email.body || '' ),
  516. argList = [];
  517. // Build the e-mail parameters first.
  518. subject && argList.push( 'subject=' + subject );
  519. body && argList.push( 'body=' + body );
  520. argList = argList.length ? '?' + argList.join( '&' ) : '';
  521. if ( emailProtection == 'encode' ) {
  522. linkHref = [
  523. 'javascript:void(location.href=\'mailto:\'+', // jshint ignore:line
  524. protectEmailAddressAsEncodedString( address )
  525. ];
  526. // parameters are optional.
  527. argList && linkHref.push( '+\'', escapeSingleQuote( argList ), '\'' );
  528. linkHref.push( ')' );
  529. } else {
  530. linkHref = [ 'mailto:', address, argList ];
  531. }
  532. break;
  533. default:
  534. // Separating name and domain.
  535. var nameAndDomain = address.split( '@', 2 );
  536. email.name = nameAndDomain[ 0 ];
  537. email.domain = nameAndDomain[ 1 ];
  538. linkHref = [ 'javascript:', protectEmailLinkAsFunction( editor, email ) ]; // jshint ignore:line
  539. }
  540. set[ 'data-cke-saved-href' ] = linkHref.join( '' );
  541. break;
  542. }
  543. // Popups and target.
  544. if ( data.target ) {
  545. if ( data.target.type == 'popup' ) {
  546. var onclickList = [
  547. 'window.open(this.href, \'', data.target.name || '', '\', \''
  548. ],
  549. featureList = [
  550. 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen', 'scrollbars', 'dependent'
  551. ],
  552. featureLength = featureList.length,
  553. addFeature = function( featureName ) {
  554. if ( data.target[ featureName ] )
  555. featureList.push( featureName + '=' + data.target[ featureName ] );
  556. };
  557. for ( var i = 0; i < featureLength; i++ )
  558. featureList[ i ] = featureList[ i ] + ( data.target[ featureList[ i ] ] ? '=yes' : '=no' );
  559. addFeature( 'width' );
  560. addFeature( 'left' );
  561. addFeature( 'height' );
  562. addFeature( 'top' );
  563. onclickList.push( featureList.join( ',' ), '\'); return false;' );
  564. set[ 'data-cke-pa-onclick' ] = onclickList.join( '' );
  565. }
  566. else if ( data.target.type != 'notSet' && data.target.name ) {
  567. set.target = data.target.name;
  568. }
  569. }
  570. // Advanced attributes.
  571. if ( data.advanced ) {
  572. for ( var a in advAttrNames ) {
  573. var val = data.advanced[ advAttrNames[ a ] ];
  574. //console.log(a);
  575. if( a == 'rel' && val === true ) val = 'nofollow';
  576. if ( val )
  577. set[ a ] = val;
  578. }
  579. if ( set.name )
  580. set[ 'data-cke-saved-name' ] = set.name;
  581. }
  582. // Browser need the "href" fro copy/paste link to work. (#6641)
  583. if ( set[ 'data-cke-saved-href' ] )
  584. set.href = set[ 'data-cke-saved-href' ];
  585. var removed = {
  586. target: 1,
  587. onclick: 1,
  588. 'data-cke-pa-onclick': 1,
  589. 'data-cke-saved-name': 1
  590. };
  591. if ( data.advanced )
  592. CKEDITOR.tools.extend( removed, advAttrNames );
  593. // Remove all attributes which are not currently set.
  594. for ( var s in set )
  595. delete removed[ s ];
  596. return {
  597. set: set,
  598. removed: CKEDITOR.tools.objectKeys( removed )
  599. };
  600. }
  601. };
  602. // TODO Much probably there's no need to expose these as public objects.
  603. CKEDITOR.unlinkCommand = function() {};
  604. CKEDITOR.unlinkCommand.prototype = {
  605. exec: function( editor ) {
  606. var style = new CKEDITOR.style( { element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1 } );
  607. editor.removeStyle( style );
  608. },
  609. refresh: function( editor, path ) {
  610. // Despite our initial hope, document.queryCommandEnabled() does not work
  611. // for this in Firefox. So we must detect the state by element paths.
  612. var element = path.lastElement && path.lastElement.getAscendant( 'a', true );
  613. if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) && element.getChildCount() )
  614. this.setState( CKEDITOR.TRISTATE_OFF );
  615. else
  616. this.setState( CKEDITOR.TRISTATE_DISABLED );
  617. },
  618. contextSensitive: 1,
  619. startDisabled: 1,
  620. requiredContent: 'a[href]'
  621. };
  622. CKEDITOR.removeAnchorCommand = function() {};
  623. CKEDITOR.removeAnchorCommand.prototype = {
  624. exec: function( editor ) {
  625. var sel = editor.getSelection(),
  626. bms = sel.createBookmarks(),
  627. anchor;
  628. if ( sel && ( anchor = sel.getSelectedElement() ) && ( !anchor.getChildCount() ? CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, anchor ) : anchor.is( 'a' ) ) )
  629. anchor.remove( 1 );
  630. else {
  631. if ( ( anchor = CKEDITOR.plugins.link.getSelectedLink( editor ) ) ) {
  632. if ( anchor.hasAttribute( 'href' ) ) {
  633. anchor.removeAttributes( { name: 1, 'data-cke-saved-name': 1 } );
  634. anchor.removeClass( 'cke_anchor' );
  635. } else {
  636. anchor.remove( 1 );
  637. }
  638. }
  639. }
  640. sel.selectBookmarks( bms );
  641. },
  642. requiredContent: 'a[name]'
  643. };
  644. CKEDITOR.tools.extend( CKEDITOR.config, {
  645. /**
  646. * Whether to show the Advanced tab in the Link dialog window.
  647. *
  648. * @cfg {Boolean} [linkShowAdvancedTab=true]
  649. * @member CKEDITOR.config
  650. */
  651. linkShowAdvancedTab: true,
  652. /**
  653. * Whether to show the Target tab in the Link dialog window.
  654. *
  655. * @cfg {Boolean} [linkShowTargetTab=true]
  656. * @member CKEDITOR.config
  657. */
  658. linkShowTargetTab: true
  659. /**
  660. * Whether JavaScript code is allowed as a `href` attribute in an anchor tag.
  661. * With this option enabled it is possible to create links like:
  662. *
  663. * <a href="javascript:alert('Hello world!')">hello world</a>
  664. *
  665. * By default JavaScript links are not allowed and will not pass
  666. * the Link dialog window validation.
  667. *
  668. * @since 4.4.1
  669. * @cfg {Boolean} [linkJavaScriptLinksAllowed=false]
  670. * @member CKEDITOR.config
  671. */
  672. } );
  673. } )();