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