2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
6 CKEDITOR
.plugins
.add( 'link',
8 init : function( editor
)
10 // Add the link and unlink buttons.
11 editor
.addCommand( 'link', new CKEDITOR
.dialogCommand( 'link' ) );
12 editor
.addCommand( 'anchor', new CKEDITOR
.dialogCommand( 'anchor' ) );
13 editor
.addCommand( 'unlink', new CKEDITOR
.unlinkCommand() );
14 editor
.addCommand( 'removeAnchor', new CKEDITOR
.removeAnchorCommand() );
15 editor
.ui
.addButton( 'Link',
17 label
: editor
.lang
.link
.toolbar
,
20 editor
.ui
.addButton( 'Unlink',
22 label
: editor
.lang
.unlink
,
25 editor
.ui
.addButton( 'Anchor',
27 label
: editor
.lang
.anchor
.toolbar
,
30 CKEDITOR
.dialog
.add( 'link', this.path
+ 'dialogs/link.js' );
31 CKEDITOR
.dialog
.add( 'anchor', this.path
+ 'dialogs/anchor.js' );
33 // Add the CSS styles for anchor placeholders.
35 var side
= ( editor
.lang
.dir
== 'rtl' ? 'right' : 'left' );
37 'background:url(' + CKEDITOR
.getUrl( this.path
+ 'images/anchor.gif' ) + ') no-repeat ' + side
+ ' center;' +
38 'border:1px dotted #00f;';
41 'a.cke_anchor,a.cke_anchor_empty' +
42 // IE6 breaks with the following selectors.
43 ( ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 7 ) ? '' :
44 ',a[name],a[data-cke-saved-name]' ) +
47 'padding-' + side
+ ':18px;' +
48 // Show the arrow cursor for the anchor image (FF at least).
52 'a.cke_anchor_empty' +
54 // Make empty anchor selectable on IE.
55 'display:inline-block;' +
63 // The default line-height on IE.
65 // Opera works better with "middle" (even if not perfect)
66 'vertical-align:' + ( CKEDITOR
.env
.opera
? 'middle' : 'text-bottom' ) + ';' +
69 // Register selection change handler for the unlink button.
70 editor
.on( 'selectionChange', function( evt
)
72 if ( editor
.readOnly
)
76 * Despite our initial hope, document.queryCommandEnabled() does not work
77 * for this in Firefox. So we must detect the state by element paths.
79 var command
= editor
.getCommand( 'unlink' ),
80 element
= evt
.data
.path
.lastElement
&& evt
.data
.path
.lastElement
.getAscendant( 'a', true );
81 if ( element
&& element
.getName() == 'a' && element
.getAttribute( 'href' ) && element
.getChildCount() )
82 command
.setState( CKEDITOR
.TRISTATE_OFF
);
84 command
.setState( CKEDITOR
.TRISTATE_DISABLED
);
87 editor
.on( 'doubleclick', function( evt
)
89 var element
= CKEDITOR
.plugins
.link
.getSelectedLink( editor
) || evt
.data
.element
;
91 if ( !element
.isReadOnly() )
93 if ( element
.is( 'a' ) )
95 evt
.data
.dialog
= ( element
.getAttribute( 'name' ) && ( !element
.getAttribute( 'href' ) || !element
.getChildCount() ) ) ? 'anchor' : 'link';
96 editor
.getSelection().selectElement( element
);
98 else if ( CKEDITOR
.plugins
.link
.tryRestoreFakeAnchor( editor
, element
) )
99 evt
.data
.dialog
= 'anchor';
103 // If the "menu" plugin is loaded, register the menu items.
104 if ( editor
.addMenuItems
)
110 label
: editor
.lang
.anchor
.menu
,
118 label
: editor
.lang
.anchor
.remove
,
119 command
: 'removeAnchor',
126 label
: editor
.lang
.link
.menu
,
134 label
: editor
.lang
.unlink
,
142 // If the "contextmenu" plugin is loaded, register the listeners.
143 if ( editor
.contextMenu
)
145 editor
.contextMenu
.addListener( function( element
, selection
)
147 if ( !element
|| element
.isReadOnly() )
150 var anchor
= CKEDITOR
.plugins
.link
.tryRestoreFakeAnchor( editor
, element
);
152 if ( !anchor
&& !( anchor
= CKEDITOR
.plugins
.link
.getSelectedLink( editor
) ) )
157 if ( anchor
.getAttribute( 'href' ) && anchor
.getChildCount() )
158 menu
= { link
: CKEDITOR
.TRISTATE_OFF
, unlink
: CKEDITOR
.TRISTATE_OFF
};
160 if ( anchor
&& anchor
.hasAttribute( 'name' ) )
161 menu
.anchor
= menu
.removeAnchor
= CKEDITOR
.TRISTATE_OFF
;
168 afterInit : function( editor
)
170 // Register a filter to displaying placeholders after mode change.
172 var dataProcessor
= editor
.dataProcessor
,
173 dataFilter
= dataProcessor
&& dataProcessor
.dataFilter
,
174 htmlFilter
= dataProcessor
&& dataProcessor
.htmlFilter
,
175 pathFilters
= editor
._
.elementsPath
&& editor
._
.elementsPath
.filters
;
183 a : function( element
)
185 var attributes
= element
.attributes
;
186 if ( !attributes
.name
)
189 var isEmpty
= !element
.children
.length
;
191 if ( CKEDITOR
.plugins
.link
.synAnchorSelector
)
193 // IE needs a specific class name to be applied
194 // to the anchors, for appropriate styling.
195 var ieClass
= isEmpty
? 'cke_anchor_empty' : 'cke_anchor';
196 var cls
= attributes
[ 'class' ];
197 if ( attributes
.name
&& ( !cls
|| cls
.indexOf( ieClass
) < 0 ) )
198 attributes
[ 'class' ] = ( cls
|| '' ) + ' ' + ieClass
;
200 if ( isEmpty
&& CKEDITOR
.plugins
.link
.emptyAnchorFix
)
202 attributes
.contenteditable
= 'false';
203 attributes
[ 'data-cke-editable' ] = 1;
206 else if ( CKEDITOR
.plugins
.link
.fakeAnchor
&& isEmpty
)
207 return editor
.createFakeParserElement( element
, 'cke_anchor', 'anchor' );
215 if ( CKEDITOR
.plugins
.link
.emptyAnchorFix
&& htmlFilter
)
221 a : function( element
)
223 delete element
.attributes
.contenteditable
;
231 pathFilters
.push( function( element
, name
)
235 if ( CKEDITOR
.plugins
.link
.tryRestoreFakeAnchor( editor
, element
) ||
236 ( element
.getAttribute( 'name' ) && ( !element
.getAttribute( 'href' ) || !element
.getChildCount() ) ) )
245 requires
: [ 'fakeobjects' ]
248 CKEDITOR
.plugins
.link
=
251 * Get the surrounding link element of current selection.
253 * @example CKEDITOR.plugins.link.getSelectedLink( editor );
255 * The following selection will all return the link element.
257 * <a href="#">li^nk</a>
258 * <a href="#">[link]</a>
259 * text[<a href="#">link]</a>
260 * <a href="#">li[nk</a>]
261 * [<b><a href="#">li]nk</a></b>]
262 * [<a href="#"><b>li]nk</b></a>
265 getSelectedLink : function( editor
)
269 var selection
= editor
.getSelection();
270 if ( selection
.getType() == CKEDITOR
.SELECTION_ELEMENT
)
272 var selectedElement
= selection
.getSelectedElement();
273 if ( selectedElement
.is( 'a' ) )
274 return selectedElement
;
277 var range
= selection
.getRanges( true )[ 0 ];
278 range
.shrink( CKEDITOR
.SHRINK_TEXT
);
279 var root
= range
.getCommonAncestor();
280 return root
.getAscendant( 'a', true );
282 catch( e
) { return null; }
285 // Opera and WebKit don't make it possible to select empty anchors. Fake
286 // elements must be used for them.
287 fakeAnchor
: CKEDITOR
.env
.opera
|| CKEDITOR
.env
.webkit
,
289 // For browsers that don't support CSS3 a[name]:empty(), note IE9 is included because of #7783.
290 synAnchorSelector
: CKEDITOR
.env
.ie
,
292 // For browsers that have editing issue with empty anchor.
293 emptyAnchorFix
: CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 8,
295 tryRestoreFakeAnchor : function( editor
, element
)
297 if ( element
&& element
.data( 'cke-real-element-type' ) && element
.data( 'cke-real-element-type' ) == 'anchor' )
299 var link
= editor
.restoreRealElement( element
);
300 if ( link
.data( 'cke-saved-name' ) )
306 CKEDITOR
.unlinkCommand = function(){};
307 CKEDITOR
.unlinkCommand
.prototype =
310 exec : function( editor
)
313 * execCommand( 'unlink', ... ) in Firefox leaves behind <span> tags at where
314 * the <a> was, so again we have to remove the link ourselves. (See #430)
316 * TODO: Use the style system when it's complete. Let's use execCommand()
317 * as a stopgap solution for now.
319 var selection
= editor
.getSelection(),
320 bookmarks
= selection
.createBookmarks(),
321 ranges
= selection
.getRanges(),
325 for ( var i
= 0 ; i
< ranges
.length
; i
++ )
327 rangeRoot
= ranges
[i
].getCommonAncestor( true );
328 element
= rangeRoot
.getAscendant( 'a', true );
331 ranges
[i
].selectNodeContents( element
);
334 selection
.selectRanges( ranges
);
335 editor
.document
.$.execCommand( 'unlink', false, null );
336 selection
.selectBookmarks( bookmarks
);
342 CKEDITOR
.removeAnchorCommand = function(){};
343 CKEDITOR
.removeAnchorCommand
.prototype =
346 exec : function( editor
)
348 var sel
= editor
.getSelection(),
349 bms
= sel
.createBookmarks(),
351 if ( sel
&& ( anchor
= sel
.getSelectedElement() ) && ( CKEDITOR
.plugins
.link
.fakeAnchor
&& !anchor
.getChildCount() ? CKEDITOR
.plugins
.link
.tryRestoreFakeAnchor( editor
, anchor
) : anchor
.is( 'a' ) ) )
355 if ( ( anchor
= CKEDITOR
.plugins
.link
.getSelectedLink( editor
) ) )
357 if ( anchor
.hasAttribute( 'href' ) )
359 anchor
.removeAttributes( { name
: 1, 'data-cke-saved-name' : 1 } );
360 anchor
.removeClass( 'cke_anchor' );
366 sel
.selectBookmarks( bms
);
370 CKEDITOR
.tools
.extend( CKEDITOR
.config
,
372 linkShowAdvancedTab
: true,
373 linkShowTargetTab
: true