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