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