]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blame - sources/plugins/menu/plugin.js
Update to 4.7.3
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / menu / plugin.js
CommitLineData
c63493c8
IB
1/**\r
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.\r
3 * For licensing, see LICENSE.md or http://ckeditor.com/license\r
4 */\r
5\r
6CKEDITOR.plugins.add( 'menu', {\r
7 requires: 'floatpanel',\r
8\r
9 beforeInit: function( editor ) {\r
10 var groups = editor.config.menu_groups.split( ',' ),\r
11 groupsOrder = editor._.menuGroups = {},\r
12 menuItems = editor._.menuItems = {};\r
13\r
14 for ( var i = 0; i < groups.length; i++ )\r
15 groupsOrder[ groups[ i ] ] = i + 1;\r
16\r
17 /**\r
18 * Registers an item group to the editor context menu in order to make it\r
19 * possible to associate it with menu items later.\r
20 *\r
21 * @param {String} name Specify a group name.\r
22 * @param {Number} [order=100] Define the display sequence of this group\r
23 * inside the menu. A smaller value gets displayed first.\r
24 * @member CKEDITOR.editor\r
25 */\r
26 editor.addMenuGroup = function( name, order ) {\r
27 groupsOrder[ name ] = order || 100;\r
28 };\r
29\r
30 /**\r
31 * Adds an item from the specified definition to the editor context menu.\r
32 *\r
33 * @method\r
34 * @param {String} name The menu item name.\r
35 * @param {Object} definition The menu item definition.\r
36 * @member CKEDITOR.editor\r
37 */\r
38 editor.addMenuItem = function( name, definition ) {\r
39 if ( groupsOrder[ definition.group ] )\r
40 menuItems[ name ] = new CKEDITOR.menuItem( this, name, definition );\r
41 };\r
42\r
43 /**\r
44 * Adds one or more items from the specified definition object to the editor context menu.\r
45 *\r
46 * @method\r
47 * @param {Object} definitions Object where keys are used as itemName and corresponding values as definition for a {@link #addMenuItem} call.\r
48 * @member CKEDITOR.editor\r
49 */\r
50 editor.addMenuItems = function( definitions ) {\r
51 for ( var itemName in definitions ) {\r
52 this.addMenuItem( itemName, definitions[ itemName ] );\r
53 }\r
54 };\r
55\r
56 /**\r
57 * Retrieves a particular menu item definition from the editor context menu.\r
58 *\r
59 * @method\r
60 * @param {String} name The name of the desired menu item.\r
61 * @returns {Object}\r
62 * @member CKEDITOR.editor\r
63 */\r
64 editor.getMenuItem = function( name ) {\r
65 return menuItems[ name ];\r
66 };\r
67\r
68 /**\r
69 * Removes a particular menu item added before from the editor context menu.\r
70 *\r
71 * @since 3.6.1\r
72 * @method\r
73 * @param {String} name The name of the desired menu item.\r
74 * @member CKEDITOR.editor\r
75 */\r
76 editor.removeMenuItem = function( name ) {\r
77 delete menuItems[ name ];\r
78 };\r
79 }\r
80} );\r
81\r
82( function() {\r
83 var menuItemSource = '<span class="cke_menuitem">' +\r
84 '<a id="{id}"' +\r
85 ' class="cke_menubutton cke_menubutton__{name} cke_menubutton_{state} {cls}" href="{href}"' +\r
86 ' title="{title}"' +\r
87 ' tabindex="-1"' +\r
88 ' _cke_focus=1' +\r
89 ' hidefocus="true"' +\r
90 ' role="{role}"' +\r
91 ' aria-label="{label}"' +\r
92 ' aria-describedby="{id}_description"' +\r
93 ' aria-haspopup="{hasPopup}"' +\r
94 ' aria-disabled="{disabled}"' +\r
95 ' {ariaChecked}' +\r
96 ' draggable="false"';\r
97\r
98 // Some browsers don't cancel key events in the keydown but in the\r
99 // keypress.\r
100 // TODO: Check if really needed.\r
101 if ( CKEDITOR.env.gecko && CKEDITOR.env.mac )\r
102 menuItemSource += ' onkeypress="return false;"';\r
103\r
104 // With Firefox, we need to force the button to redraw, otherwise it\r
1794320d 105 // will remain in the focus state. Also we some extra help to prevent dragging (http://dev.ckeditor.com/ticket/10373).\r
c63493c8
IB
106 if ( CKEDITOR.env.gecko ) {\r
107 menuItemSource += ( ' onblur="this.style.cssText = this.style.cssText;"' +\r
108 ' ondragstart="return false;"' );\r
109 }\r
110\r
1794320d 111 // http://dev.ckeditor.com/ticket/188\r
c63493c8
IB
112 menuItemSource += ' onmouseover="CKEDITOR.tools.callFunction({hoverFn},{index});"' +\r
113 ' onmouseout="CKEDITOR.tools.callFunction({moveOutFn},{index});" ' +\r
114 ( CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick' ) +\r
115 '="CKEDITOR.tools.callFunction({clickFn},{index}); return false;"' +\r
116 '>';\r
117\r
118 menuItemSource +=\r
119 //'' +\r
120 '<span class="cke_menubutton_inner">' +\r
121 '<span class="cke_menubutton_icon">' +\r
122 '<span class="cke_button_icon cke_button__{iconName}_icon" style="{iconStyle}"></span>' +\r
123 '</span>' +\r
124 '<span class="cke_menubutton_label">' +\r
125 '{label}' +\r
126 '</span>' +\r
127 '{shortcutHtml}' +\r
128 '{arrowHtml}' +\r
129 '</span>' +\r
130 '</a><span id="{id}_description" class="cke_voice_label" aria-hidden="false">{ariaShortcut}</span></span>';\r
131\r
132 var menuArrowSource = '<span class="cke_menuarrow">' +\r
133 '<span>{label}</span>' +\r
134 '</span>';\r
135\r
136 var menuShortcutSource = '<span class="cke_menubutton_label cke_menubutton_shortcut">' +\r
137 '{shortcut}' +\r
138 '</span>';\r
139\r
140 var menuItemTpl = CKEDITOR.addTemplate( 'menuItem', menuItemSource ),\r
141 menuArrowTpl = CKEDITOR.addTemplate( 'menuArrow', menuArrowSource ),\r
142 menuShortcutTpl = CKEDITOR.addTemplate( 'menuShortcut', menuShortcutSource );\r
143\r
144 /**\r
145 * @class\r
146 * @todo\r
147 */\r
148 CKEDITOR.menu = CKEDITOR.tools.createClass( {\r
149 /**\r
150 * @constructor\r
151 */\r
152 $: function( editor, definition ) {\r
153 definition = this._.definition = definition || {};\r
154 this.id = CKEDITOR.tools.getNextId();\r
155\r
156 this.editor = editor;\r
157 this.items = [];\r
158 this._.listeners = [];\r
159\r
160 this._.level = definition.level || 1;\r
161\r
162 var panelDefinition = CKEDITOR.tools.extend( {}, definition.panel, {\r
163 css: [ CKEDITOR.skin.getPath( 'editor' ) ],\r
164 level: this._.level - 1,\r
165 block: {}\r
166 } );\r
167\r
168 var attrs = panelDefinition.block.attributes = ( panelDefinition.attributes || {} );\r
169 // Provide default role of 'menu'.\r
170 !attrs.role && ( attrs.role = 'menu' );\r
171 this._.panelDefinition = panelDefinition;\r
172 },\r
173\r
174 _: {\r
175 onShow: function() {\r
176 var selection = this.editor.getSelection(),\r
177 start = selection && selection.getStartElement(),\r
178 path = this.editor.elementPath(),\r
179 listeners = this._.listeners;\r
180\r
181 this.removeAll();\r
182 // Call all listeners, filling the list of items to be displayed.\r
183 for ( var i = 0; i < listeners.length; i++ ) {\r
184 var listenerItems = listeners[ i ]( start, selection, path );\r
185\r
186 if ( listenerItems ) {\r
187 for ( var itemName in listenerItems ) {\r
188 var item = this.editor.getMenuItem( itemName );\r
189\r
190 if ( item && ( !item.command || this.editor.getCommand( item.command ).state ) ) {\r
191 item.state = listenerItems[ itemName ];\r
192 this.add( item );\r
193 }\r
194 }\r
195 }\r
196 }\r
197 },\r
198\r
199 onClick: function( item ) {\r
200 this.hide();\r
201\r
202 if ( item.onClick )\r
203 item.onClick();\r
204 else if ( item.command )\r
205 this.editor.execCommand( item.command );\r
206 },\r
207\r
208 onEscape: function( keystroke ) {\r
209 var parent = this.parent;\r
210 // 1. If it's sub-menu, close it, with focus restored on this.\r
211 // 2. In case of a top-menu, close it, with focus returned to page.\r
212 if ( parent )\r
213 parent._.panel.hideChild( 1 );\r
214 else if ( keystroke == 27 )\r
215 this.hide( 1 );\r
216\r
217 return false;\r
218 },\r
219\r
220 onHide: function() {\r
221 this.onHide && this.onHide();\r
222 },\r
223\r
224 showSubMenu: function( index ) {\r
225 var menu = this._.subMenu,\r
226 item = this.items[ index ],\r
227 subItemDefs = item.getItems && item.getItems();\r
228\r
229 // If this item has no subitems, we just hide the submenu, if\r
230 // available, and return back.\r
231 if ( !subItemDefs ) {\r
232 // Hide sub menu with focus returned.\r
233 this._.panel.hideChild( 1 );\r
234 return;\r
235 }\r
236\r
237 // Create the submenu, if not available, or clean the existing\r
238 // one.\r
239 if ( menu )\r
240 menu.removeAll();\r
241 else {\r
242 menu = this._.subMenu = new CKEDITOR.menu( this.editor, CKEDITOR.tools.extend( {}, this._.definition, { level: this._.level + 1 }, true ) );\r
243 menu.parent = this;\r
244 menu._.onClick = CKEDITOR.tools.bind( this._.onClick, this );\r
245 }\r
246\r
247 // Add all submenu items to the menu.\r
248 for ( var subItemName in subItemDefs ) {\r
249 var subItem = this.editor.getMenuItem( subItemName );\r
250 if ( subItem ) {\r
251 subItem.state = subItemDefs[ subItemName ];\r
252 menu.add( subItem );\r
253 }\r
254 }\r
255\r
256 // Get the element representing the current item.\r
257 var element = this._.panel.getBlock( this.id ).element.getDocument().getById( this.id + String( index ) );\r
258\r
259 // Show the submenu.\r
260 // This timeout is needed to give time for the sub-menu get\r
1794320d 261 // focus when JAWS is running. (http://dev.ckeditor.com/ticket/9844)\r
c63493c8
IB
262 setTimeout( function() {\r
263 menu.show( element, 2 );\r
264 }, 0 );\r
265 }\r
266 },\r
267\r
268 proto: {\r
269 /**\r
270 * Adds an item.\r
271 *\r
272 * @param item\r
273 */\r
274 add: function( item ) {\r
275 // Later we may sort the items, but Array#sort is not stable in\r
276 // some browsers, here we're forcing the original sequence with\r
1794320d 277 // 'order' attribute if it hasn't been assigned. (http://dev.ckeditor.com/ticket/3868)\r
c63493c8
IB
278 if ( !item.order )\r
279 item.order = this.items.length;\r
280\r
281 this.items.push( item );\r
282 },\r
283\r
284 /**\r
285 * Removes all items.\r
286 */\r
287 removeAll: function() {\r
288 this.items = [];\r
289 },\r
290\r
291 /**\r
292 * Shows the menu in given location.\r
293 *\r
294 * @param {CKEDITOR.dom.element} offsetParent\r
295 * @param {Number} [corner]\r
296 * @param {Number} [offsetX]\r
297 * @param {Number} [offsetY]\r
298 */\r
299 show: function( offsetParent, corner, offsetX, offsetY ) {\r
300 // Not for sub menu.\r
301 if ( !this.parent ) {\r
302 this._.onShow();\r
303 // Don't menu with zero items.\r
304 if ( !this.items.length )\r
305 return;\r
306 }\r
307\r
308 corner = corner || ( this.editor.lang.dir == 'rtl' ? 2 : 1 );\r
309\r
310 var items = this.items,\r
311 editor = this.editor,\r
312 panel = this._.panel,\r
313 element = this._.element;\r
314\r
315 // Create the floating panel for this menu.\r
316 if ( !panel ) {\r
317 panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor, CKEDITOR.document.getBody(), this._.panelDefinition, this._.level );\r
318\r
319 panel.onEscape = CKEDITOR.tools.bind( function( keystroke ) {\r
320 if ( this._.onEscape( keystroke ) === false )\r
321 return false;\r
322 }, this );\r
323\r
324 panel.onShow = function() {\r
325 // Menu need CSS resets, compensate class name.\r
326 var holder = panel._.panel.getHolderElement();\r
327 holder.getParent().addClass( 'cke' ).addClass( 'cke_reset_all' );\r
328 };\r
329\r
330 panel.onHide = CKEDITOR.tools.bind( function() {\r
331 this._.onHide && this._.onHide();\r
332 }, this );\r
333\r
334 // Create an autosize block inside the panel.\r
335 var block = panel.addBlock( this.id, this._.panelDefinition.block );\r
336 block.autoSize = true;\r
337\r
338 var keys = block.keys;\r
339 keys[ 40 ] = 'next'; // ARROW-DOWN\r
340 keys[ 9 ] = 'next'; // TAB\r
341 keys[ 38 ] = 'prev'; // ARROW-UP\r
342 keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB\r
343 keys[ ( editor.lang.dir == 'rtl' ? 37 : 39 ) ] = CKEDITOR.env.ie ? 'mouseup' : 'click'; // ARROW-RIGHT/ARROW-LEFT(rtl)\r
344 keys[ 32 ] = CKEDITOR.env.ie ? 'mouseup' : 'click'; // SPACE\r
1794320d 345 CKEDITOR.env.ie && ( keys[ 13 ] = 'mouseup' ); // Manage ENTER, since onclick is blocked in IE (http://dev.ckeditor.com/ticket/8041).\r
c63493c8
IB
346\r
347 element = this._.element = block.element;\r
348\r
349 var elementDoc = element.getDocument();\r
350 elementDoc.getBody().setStyle( 'overflow', 'hidden' );\r
351 elementDoc.getElementsByTag( 'html' ).getItem( 0 ).setStyle( 'overflow', 'hidden' );\r
352\r
353 this._.itemOverFn = CKEDITOR.tools.addFunction( function( index ) {\r
354 clearTimeout( this._.showSubTimeout );\r
355 this._.showSubTimeout = CKEDITOR.tools.setTimeout( this._.showSubMenu, editor.config.menu_subMenuDelay || 400, this, [ index ] );\r
356 }, this );\r
357\r
358 this._.itemOutFn = CKEDITOR.tools.addFunction( function() {\r
359 clearTimeout( this._.showSubTimeout );\r
360 }, this );\r
361\r
362 this._.itemClickFn = CKEDITOR.tools.addFunction( function( index ) {\r
363 var item = this.items[ index ];\r
364\r
365 if ( item.state == CKEDITOR.TRISTATE_DISABLED ) {\r
366 this.hide( 1 );\r
367 return;\r
368 }\r
369\r
370 if ( item.getItems )\r
371 this._.showSubMenu( index );\r
372 else\r
373 this._.onClick( item );\r
374 }, this );\r
375 }\r
376\r
377 // Put the items in the right order.\r
378 sortItems( items );\r
379\r
380 // Apply the editor mixed direction status to menu.\r
381 var path = editor.elementPath(),\r
382 mixedDirCls = ( path && path.direction() != editor.lang.dir ) ? ' cke_mixed_dir_content' : '';\r
383\r
384 // Build the HTML that composes the menu and its items.\r
385 var output = [ '<div class="cke_menu' + mixedDirCls + '" role="presentation">' ];\r
386\r
387 var length = items.length,\r
388 lastGroup = length && items[ 0 ].group;\r
389\r
390 for ( var i = 0; i < length; i++ ) {\r
391 var item = items[ i ];\r
392 if ( lastGroup != item.group ) {\r
393 output.push( '<div class="cke_menuseparator" role="separator"></div>' );\r
394 lastGroup = item.group;\r
395 }\r
396\r
397 item.render( this, i, output );\r
398 }\r
399\r
400 output.push( '</div>' );\r
401\r
402 // Inject the HTML inside the panel.\r
403 element.setHtml( output.join( '' ) );\r
404\r
405 CKEDITOR.ui.fire( 'ready', this );\r
406\r
407 // Show the panel.\r
408 if ( this.parent )\r
409 this.parent._.panel.showAsChild( panel, this.id, offsetParent, corner, offsetX, offsetY );\r
410 else\r
411 panel.showBlock( this.id, offsetParent, corner, offsetX, offsetY );\r
412\r
413 editor.fire( 'menuShow', [ panel ] );\r
414 },\r
415\r
416 /**\r
417 * Adds a callback executed on opening the menu. Items\r
418 * returned by that callback are added to the menu.\r
419 *\r
420 * @param {Function} listenerFn\r
421 * @param {CKEDITOR.dom.element} listenerFn.startElement The selection start anchor element.\r
422 * @param {CKEDITOR.dom.selection} listenerFn.selection The current selection.\r
423 * @param {CKEDITOR.dom.elementPath} listenerFn.path The current elements path.\r
424 * @param listenerFn.return Object (`commandName` => `state`) of items that should be added to the menu.\r
425 */\r
426 addListener: function( listenerFn ) {\r
427 this._.listeners.push( listenerFn );\r
428 },\r
429\r
430 /**\r
431 * Hides the menu.\r
432 *\r
433 * @param {Boolean} [returnFocus]\r
434 */\r
435 hide: function( returnFocus ) {\r
436 this._.onHide && this._.onHide();\r
437 this._.panel && this._.panel.hide( returnFocus );\r
438 }\r
439 }\r
440 } );\r
441\r
442 function sortItems( items ) {\r
443 items.sort( function( itemA, itemB ) {\r
444 if ( itemA.group < itemB.group )\r
445 return -1;\r
446 else if ( itemA.group > itemB.group )\r
447 return 1;\r
448\r
449 return itemA.order < itemB.order ? -1 : itemA.order > itemB.order ? 1 : 0;\r
450 } );\r
451 }\r
452\r
453 /**\r
454 * @class\r
455 * @todo\r
456 */\r
457 CKEDITOR.menuItem = CKEDITOR.tools.createClass( {\r
458 $: function( editor, name, definition ) {\r
459 CKEDITOR.tools.extend( this, definition,\r
460 // Defaults\r
461 {\r
462 order: 0,\r
463 className: 'cke_menubutton__' + name\r
464 } );\r
465\r
466 // Transform the group name into its order number.\r
467 this.group = editor._.menuGroups[ this.group ];\r
468\r
469 this.editor = editor;\r
470 this.name = name;\r
471 },\r
472\r
473 proto: {\r
474 render: function( menu, index, output ) {\r
475 var id = menu.id + String( index ),\r
476 state = ( typeof this.state == 'undefined' ) ? CKEDITOR.TRISTATE_OFF : this.state,\r
477 ariaChecked = '',\r
478 editor = this.editor,\r
479 keystroke,\r
480 command,\r
481 shortcut;\r
482\r
483 var stateName = state == CKEDITOR.TRISTATE_ON ? 'on' : state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' : 'off';\r
484\r
485 if ( this.role in { menuitemcheckbox: 1, menuitemradio: 1 } )\r
486 ariaChecked = ' aria-checked="' + ( state == CKEDITOR.TRISTATE_ON ? 'true' : 'false' ) + '"';\r
487\r
488 var hasSubMenu = this.getItems;\r
489 // ltr: BLACK LEFT-POINTING POINTER\r
490 // rtl: BLACK RIGHT-POINTING POINTER\r
491 var arrowLabel = '&#' + ( this.editor.lang.dir == 'rtl' ? '9668' : '9658' ) + ';';\r
492\r
493 var iconName = this.name;\r
494 if ( this.icon && !( /\./ ).test( this.icon ) )\r
495 iconName = this.icon;\r
496\r
497 if ( this.command ) {\r
498 command = editor.getCommand( this.command );\r
499 keystroke = editor.getCommandKeystroke( command );\r
500\r
501 if ( keystroke ) {\r
502 shortcut = CKEDITOR.tools.keystrokeToString( editor.lang.common.keyboard, keystroke );\r
503 }\r
504 }\r
505\r
506 var params = {\r
507 id: id,\r
508 name: this.name,\r
509 iconName: iconName,\r
510 label: this.label,\r
511 cls: this.className || '',\r
512 state: stateName,\r
513 hasPopup: hasSubMenu ? 'true' : 'false',\r
514 disabled: state == CKEDITOR.TRISTATE_DISABLED,\r
515 title: this.label + ( shortcut ? ' (' + shortcut.display + ')' : '' ),\r
516 ariaShortcut: shortcut ? editor.lang.common.keyboardShortcut + ' ' + shortcut.aria : '',\r
517 href: 'javascript:void(\'' + ( this.label || '' ).replace( "'" + '' ) + '\')', // jshint ignore:line\r
518 hoverFn: menu._.itemOverFn,\r
519 moveOutFn: menu._.itemOutFn,\r
520 clickFn: menu._.itemClickFn,\r
521 index: index,\r
522 iconStyle: CKEDITOR.skin.getIconStyle( iconName, ( this.editor.lang.dir == 'rtl' ), iconName == this.icon ? null : this.icon, this.iconOffset ),\r
523 shortcutHtml: shortcut ? menuShortcutTpl.output( { shortcut: shortcut.display } ) : '',\r
524 arrowHtml: hasSubMenu ? menuArrowTpl.output( { label: arrowLabel } ) : '',\r
525 role: this.role ? this.role : 'menuitem',\r
526 ariaChecked: ariaChecked\r
527 };\r
528\r
529 menuItemTpl.output( params, output );\r
530 }\r
531 }\r
532 } );\r
533\r
534} )();\r
535\r
536\r
537/**\r
538 * The amount of time, in milliseconds, the editor waits before displaying submenu\r
539 * options when moving the mouse over options that contain submenus, like the\r
540 * "Cell Properties" entry for tables.\r
541 *\r
542 * // Remove the submenu delay.\r
543 * config.menu_subMenuDelay = 0;\r
544 *\r
545 * @cfg {Number} [menu_subMenuDelay=400]\r
546 * @member CKEDITOR.config\r
547 */\r
548\r
549/**\r
550 * Fired when a menu is shown.\r
551 *\r
552 * @event menuShow\r
553 * @member CKEDITOR.editor\r
554 * @param {CKEDITOR.editor} editor This editor instance.\r
555 * @param {CKEDITOR.ui.panel[]} data\r
556 */\r
557\r
558/**\r
559 * A comma separated list of items group names to be displayed in the context\r
560 * menu. The order of items will reflect the order specified in this list if\r
561 * no priority was defined in the groups.\r
562 *\r
563 * config.menu_groups = 'clipboard,table,anchor,link,image';\r
564 *\r
565 * @cfg {String} [menu_groups=see source]\r
566 * @member CKEDITOR.config\r
567 */\r
568CKEDITOR.config.menu_groups = 'clipboard,' +\r
569 'form,' +\r
570 'tablecell,tablecellproperties,tablerow,tablecolumn,table,' +\r
571 'anchor,link,image,flash,' +\r
572 'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div';\r