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