JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.1
[ckeditor.git] / _source / plugins / link / plugin.js
1 /*\r
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 CKEDITOR.plugins.add( 'link',\r
7 {\r
8         init : function( editor )\r
9         {\r
10                 // Add the link and unlink buttons.\r
11                 editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) );\r
12                 editor.addCommand( 'anchor', new CKEDITOR.dialogCommand( 'anchor' ) );\r
13                 editor.addCommand( 'unlink', new CKEDITOR.unlinkCommand() );\r
14                 editor.addCommand( 'removeAnchor', new CKEDITOR.removeAnchorCommand() );\r
15                 editor.ui.addButton( 'Link',\r
16                         {\r
17                                 label : editor.lang.link.toolbar,\r
18                                 command : 'link'\r
19                         } );\r
20                 editor.ui.addButton( 'Unlink',\r
21                         {\r
22                                 label : editor.lang.unlink,\r
23                                 command : 'unlink'\r
24                         } );\r
25                 editor.ui.addButton( 'Anchor',\r
26                         {\r
27                                 label : editor.lang.anchor.toolbar,\r
28                                 command : 'anchor'\r
29                         } );\r
30                 CKEDITOR.dialog.add( 'link', this.path + 'dialogs/link.js' );\r
31                 CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' );\r
32 \r
33                 // Add the CSS styles for anchor placeholders.\r
34 \r
35                 var side = ( editor.lang.dir == 'rtl' ? 'right' : 'left' );\r
36                 var basicCss =\r
37                         'background:url(' + CKEDITOR.getUrl( this.path + 'images/anchor.gif' ) + ') no-repeat ' + side + ' center;' +\r
38                         'border:1px dotted #00f;';\r
39 \r
40                 editor.addCss(\r
41                         'a.cke_anchor,a.cke_anchor_empty' +\r
42                         // IE6 breaks with the following selectors.\r
43                         ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 7 ) ? '' :\r
44                                 ',a[name],a[data-cke-saved-name]' ) +\r
45                         '{' +\r
46                                 basicCss +\r
47                                 'padding-' + side + ':18px;' +\r
48                                 // Show the arrow cursor for the anchor image (FF at least).\r
49                                 'cursor:auto;' +\r
50                         '}' +\r
51                         ( CKEDITOR.env.ie ? (\r
52                                 'a.cke_anchor_empty' +\r
53                                 '{' +\r
54                                         // Make empty anchor selectable on IE.\r
55                                         'display:inline-block;' +\r
56                                 '}'\r
57                                 ) : '' ) +\r
58                         'img.cke_anchor' +\r
59                         '{' +\r
60                                 basicCss +\r
61                                 'width:16px;' +\r
62                                 'min-height:15px;' +\r
63                                 // The default line-height on IE.\r
64                                 'height:1.15em;' +\r
65                                 // Opera works better with "middle" (even if not perfect)\r
66                                 'vertical-align:' + ( CKEDITOR.env.opera ? 'middle' : 'text-bottom' ) + ';' +\r
67                         '}');\r
68 \r
69                 // Register selection change handler for the unlink button.\r
70                  editor.on( 'selectionChange', function( evt )\r
71                         {\r
72                                 if ( editor.readOnly )\r
73                                         return;\r
74 \r
75                                 /*\r
76                                  * Despite our initial hope, document.queryCommandEnabled() does not work\r
77                                  * for this in Firefox. So we must detect the state by element paths.\r
78                                  */\r
79                                 var command = editor.getCommand( 'unlink' ),\r
80                                         element = evt.data.path.lastElement && evt.data.path.lastElement.getAscendant( 'a', true );\r
81                                 if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) && element.getChildCount() )\r
82                                         command.setState( CKEDITOR.TRISTATE_OFF );\r
83                                 else\r
84                                         command.setState( CKEDITOR.TRISTATE_DISABLED );\r
85                         } );\r
86 \r
87                 editor.on( 'doubleclick', function( evt )\r
88                         {\r
89                                 var element = CKEDITOR.plugins.link.getSelectedLink( editor ) || evt.data.element;\r
90 \r
91                                 if ( !element.isReadOnly() )\r
92                                 {\r
93                                         if ( element.is( 'a' ) )\r
94                                         {\r
95                                                 evt.data.dialog = ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) ? 'anchor' : 'link';\r
96                                                 editor.getSelection().selectElement( element );\r
97                                         }\r
98                                         else if ( CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element ) )\r
99                                                 evt.data.dialog = 'anchor';\r
100                                 }\r
101                         });\r
102 \r
103                 // If the "menu" plugin is loaded, register the menu items.\r
104                 if ( editor.addMenuItems )\r
105                 {\r
106                         editor.addMenuItems(\r
107                                 {\r
108                                         anchor :\r
109                                         {\r
110                                                 label : editor.lang.anchor.menu,\r
111                                                 command : 'anchor',\r
112                                                 group : 'anchor',\r
113                                                 order : 1\r
114                                         },\r
115 \r
116                                         removeAnchor :\r
117                                         {\r
118                                                 label : editor.lang.anchor.remove,\r
119                                                 command : 'removeAnchor',\r
120                                                 group : 'anchor',\r
121                                                 order : 5\r
122                                         },\r
123 \r
124                                         link :\r
125                                         {\r
126                                                 label : editor.lang.link.menu,\r
127                                                 command : 'link',\r
128                                                 group : 'link',\r
129                                                 order : 1\r
130                                         },\r
131 \r
132                                         unlink :\r
133                                         {\r
134                                                 label : editor.lang.unlink,\r
135                                                 command : 'unlink',\r
136                                                 group : 'link',\r
137                                                 order : 5\r
138                                         }\r
139                                 });\r
140                 }\r
141 \r
142                 // If the "contextmenu" plugin is loaded, register the listeners.\r
143                 if ( editor.contextMenu )\r
144                 {\r
145                         editor.contextMenu.addListener( function( element, selection )\r
146                                 {\r
147                                         if ( !element || element.isReadOnly() )\r
148                                                 return null;\r
149 \r
150                                         var anchor = CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element );\r
151 \r
152                                         if ( !anchor && !( anchor = CKEDITOR.plugins.link.getSelectedLink( editor ) ) )\r
153                                                         return null;\r
154 \r
155                                         var menu = {};\r
156 \r
157                                         if ( anchor.getAttribute( 'href' ) && anchor.getChildCount() )\r
158                                                 menu = { link : CKEDITOR.TRISTATE_OFF, unlink : CKEDITOR.TRISTATE_OFF };\r
159 \r
160                                         if ( anchor && anchor.hasAttribute( 'name' ) )\r
161                                                 menu.anchor = menu.removeAnchor = CKEDITOR.TRISTATE_OFF;\r
162 \r
163                                         return menu;\r
164                                 });\r
165                 }\r
166         },\r
167 \r
168         afterInit : function( editor )\r
169         {\r
170                 // Register a filter to displaying placeholders after mode change.\r
171 \r
172                 var dataProcessor = editor.dataProcessor,\r
173                         dataFilter = dataProcessor && dataProcessor.dataFilter,\r
174                         htmlFilter = dataProcessor && dataProcessor.htmlFilter,\r
175                         pathFilters = editor._.elementsPath && editor._.elementsPath.filters;\r
176 \r
177                 if ( dataFilter )\r
178                 {\r
179                         dataFilter.addRules(\r
180                                 {\r
181                                         elements :\r
182                                         {\r
183                                                 a : function( element )\r
184                                                 {\r
185                                                         var attributes = element.attributes;\r
186                                                         if ( !attributes.name )\r
187                                                                 return null;\r
188 \r
189                                                         var isEmpty = !element.children.length;\r
190 \r
191                                                         if ( CKEDITOR.plugins.link.synAnchorSelector )\r
192                                                         {\r
193                                                                 // IE needs a specific class name to be applied\r
194                                                                 // to the anchors, for appropriate styling.\r
195                                                                 var ieClass = isEmpty ? 'cke_anchor_empty' : 'cke_anchor';\r
196                                                                 var cls = attributes[ 'class' ];\r
197                                                                 if ( attributes.name && ( !cls || cls.indexOf( ieClass ) < 0 ) )\r
198                                                                         attributes[ 'class' ] = ( cls || '' ) + ' ' + ieClass;\r
199 \r
200                                                                 if ( isEmpty && CKEDITOR.plugins.link.emptyAnchorFix )\r
201                                                                 {\r
202                                                                         attributes.contenteditable = 'false';\r
203                                                                         attributes[ 'data-cke-editable' ] = 1;\r
204                                                                 }\r
205                                                         }\r
206                                                         else if ( CKEDITOR.plugins.link.fakeAnchor && isEmpty )\r
207                                                                 return editor.createFakeParserElement( element, 'cke_anchor', 'anchor' );\r
208 \r
209                                                         return null;\r
210                                                 }\r
211                                         }\r
212                                 });\r
213                 }\r
214 \r
215                 if ( CKEDITOR.plugins.link.emptyAnchorFix && htmlFilter )\r
216                 {\r
217                         htmlFilter.addRules(\r
218                                 {\r
219                                         elements :\r
220                                         {\r
221                                                 a : function( element )\r
222                                                 {\r
223                                                         delete element.attributes.contenteditable;\r
224                                                 }\r
225                                         }\r
226                                 });\r
227                 }\r
228 \r
229                 if ( pathFilters )\r
230                 {\r
231                         pathFilters.push( function( element, name )\r
232                                 {\r
233                                         if ( name == 'a' )\r
234                                         {\r
235                                                 if ( CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, element ) ||\r
236                                                         ( element.getAttribute( 'name' ) && ( !element.getAttribute( 'href' ) || !element.getChildCount() ) ) )\r
237                                                 {\r
238                                                         return 'anchor';\r
239                                                 }\r
240                                         }\r
241                                 });\r
242                 }\r
243         },\r
244 \r
245         requires : [ 'fakeobjects' ]\r
246 } );\r
247 \r
248 CKEDITOR.plugins.link =\r
249 {\r
250         /**\r
251          *  Get the surrounding link element of current selection.\r
252          * @param editor\r
253          * @example CKEDITOR.plugins.link.getSelectedLink( editor );\r
254          * @since 3.2.1\r
255          * The following selection will all return the link element.\r
256          *       <pre>\r
257          *  <a href="#">li^nk</a>\r
258          *  <a href="#">[link]</a>\r
259          *  text[<a href="#">link]</a>\r
260          *  <a href="#">li[nk</a>]\r
261          *  [<b><a href="#">li]nk</a></b>]\r
262          *  [<a href="#"><b>li]nk</b></a>\r
263          * </pre>\r
264          */\r
265         getSelectedLink : function( editor )\r
266         {\r
267                 try\r
268                 {\r
269                         var selection = editor.getSelection();\r
270                         if ( selection.getType() == CKEDITOR.SELECTION_ELEMENT )\r
271                         {\r
272                                 var selectedElement = selection.getSelectedElement();\r
273                                 if ( selectedElement.is( 'a' ) )\r
274                                         return selectedElement;\r
275                         }\r
276 \r
277                         var range = selection.getRanges( true )[ 0 ];\r
278                         range.shrink( CKEDITOR.SHRINK_TEXT );\r
279                         var root = range.getCommonAncestor();\r
280                         return root.getAscendant( 'a', true );\r
281                 }\r
282                 catch( e ) { return null; }\r
283         },\r
284 \r
285         // Opera and WebKit don't make it possible to select empty anchors. Fake\r
286         // elements must be used for them.\r
287         fakeAnchor : CKEDITOR.env.opera || CKEDITOR.env.webkit,\r
288 \r
289         // For browsers that don't support CSS3 a[name]:empty(), note IE9 is included because of #7783.\r
290         synAnchorSelector : CKEDITOR.env.ie,\r
291 \r
292         // For browsers that have editing issue with empty anchor.\r
293         emptyAnchorFix : CKEDITOR.env.ie && CKEDITOR.env.version < 8,\r
294 \r
295         tryRestoreFakeAnchor : function( editor, element )\r
296         {\r
297                 if ( element && element.data( 'cke-real-element-type' ) && element.data( 'cke-real-element-type' ) == 'anchor' )\r
298                 {\r
299                         var link  = editor.restoreRealElement( element );\r
300                         if ( link.data( 'cke-saved-name' ) )\r
301                                 return link;\r
302                 }\r
303         }\r
304 };\r
305 \r
306 CKEDITOR.unlinkCommand = function(){};\r
307 CKEDITOR.unlinkCommand.prototype =\r
308 {\r
309         /** @ignore */\r
310         exec : function( editor )\r
311         {\r
312                 /*\r
313                  * execCommand( 'unlink', ... ) in Firefox leaves behind <span> tags at where\r
314                  * the <a> was, so again we have to remove the link ourselves. (See #430)\r
315                  *\r
316                  * TODO: Use the style system when it's complete. Let's use execCommand()\r
317                  * as a stopgap solution for now.\r
318                  */\r
319                 var selection = editor.getSelection(),\r
320                         bookmarks = selection.createBookmarks(),\r
321                         ranges = selection.getRanges(),\r
322                         rangeRoot,\r
323                         element;\r
324 \r
325                 for ( var i = 0 ; i < ranges.length ; i++ )\r
326                 {\r
327                         rangeRoot = ranges[i].getCommonAncestor( true );\r
328                         element = rangeRoot.getAscendant( 'a', true );\r
329                         if ( !element )\r
330                                 continue;\r
331                         ranges[i].selectNodeContents( element );\r
332                 }\r
333 \r
334                 selection.selectRanges( ranges );\r
335                 editor.document.$.execCommand( 'unlink', false, null );\r
336                 selection.selectBookmarks( bookmarks );\r
337         },\r
338 \r
339         startDisabled : true\r
340 };\r
341 \r
342 CKEDITOR.removeAnchorCommand = function(){};\r
343 CKEDITOR.removeAnchorCommand.prototype =\r
344 {\r
345         /** @ignore */\r
346         exec : function( editor )\r
347         {\r
348                 var sel = editor.getSelection(),\r
349                         bms = sel.createBookmarks(),\r
350                         anchor;\r
351                 if ( sel && ( anchor = sel.getSelectedElement() ) && ( CKEDITOR.plugins.link.fakeAnchor && !anchor.getChildCount() ? CKEDITOR.plugins.link.tryRestoreFakeAnchor( editor, anchor ) : anchor.is( 'a' ) ) )\r
352                         anchor.remove( 1 );\r
353                 else\r
354                 {\r
355                         if ( ( anchor = CKEDITOR.plugins.link.getSelectedLink( editor ) ) )\r
356                         {\r
357                                 if ( anchor.hasAttribute( 'href' ) )\r
358                                 {\r
359                                         anchor.removeAttributes( { name : 1, 'data-cke-saved-name' : 1 } );\r
360                                         anchor.removeClass( 'cke_anchor' );\r
361                                 }\r
362                                 else\r
363                                         anchor.remove( 1 );\r
364                         }\r
365                 }\r
366                 sel.selectBookmarks( bms );\r
367         }\r
368 };\r
369 \r
370 CKEDITOR.tools.extend( CKEDITOR.config,\r
371 {\r
372         linkShowAdvancedTab : true,\r
373         linkShowTargetTab : true\r
374 } );\r