@ckeditor/ckeditor5-ui 41.3.0-alpha.4 → 41.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ckeditor5-metadata.json +1 -1
- package/lang/contexts.json +12 -2
- package/lang/translations/af.po +210 -0
- package/lang/translations/ar.po +44 -4
- package/lang/translations/ast.po +44 -4
- package/lang/translations/az.po +44 -4
- package/lang/translations/bg.po +44 -4
- package/lang/translations/bn.po +44 -4
- package/lang/translations/bs.po +210 -0
- package/lang/translations/ca.po +44 -4
- package/lang/translations/cs.po +44 -4
- package/lang/translations/da.po +44 -4
- package/lang/translations/de-ch.po +44 -4
- package/lang/translations/de.po +44 -4
- package/lang/translations/el.po +44 -4
- package/lang/translations/en-au.po +44 -4
- package/lang/translations/en-gb.po +44 -4
- package/lang/translations/en.po +44 -4
- package/lang/translations/eo.po +44 -4
- package/lang/translations/es-co.po +210 -0
- package/lang/translations/es.po +44 -4
- package/lang/translations/et.po +44 -4
- package/lang/translations/eu.po +44 -4
- package/lang/translations/fa.po +44 -4
- package/lang/translations/fi.po +44 -4
- package/lang/translations/fr.po +44 -4
- package/lang/translations/gl.po +44 -4
- package/lang/translations/he.po +44 -4
- package/lang/translations/hi.po +44 -4
- package/lang/translations/hr.po +44 -4
- package/lang/translations/hu.po +44 -4
- package/lang/translations/id.po +44 -4
- package/lang/translations/it.po +44 -4
- package/lang/translations/ja.po +44 -4
- package/lang/translations/jv.po +210 -0
- package/lang/translations/km.po +44 -4
- package/lang/translations/kn.po +44 -4
- package/lang/translations/ko.po +44 -4
- package/lang/translations/ku.po +44 -4
- package/lang/translations/lt.po +44 -4
- package/lang/translations/lv.po +44 -4
- package/lang/translations/ms.po +44 -4
- package/lang/translations/nb.po +44 -4
- package/lang/translations/ne.po +44 -4
- package/lang/translations/nl.po +44 -4
- package/lang/translations/no.po +44 -4
- package/lang/translations/pl.po +44 -4
- package/lang/translations/pt-br.po +44 -4
- package/lang/translations/pt.po +44 -4
- package/lang/translations/ro.po +44 -4
- package/lang/translations/ru.po +44 -4
- package/lang/translations/sk.po +44 -4
- package/lang/translations/sl.po +44 -4
- package/lang/translations/sq.po +44 -4
- package/lang/translations/sr-latn.po +44 -4
- package/lang/translations/sr.po +44 -4
- package/lang/translations/sv.po +44 -4
- package/lang/translations/th.po +44 -4
- package/lang/translations/tk.po +44 -4
- package/lang/translations/tr.po +44 -4
- package/lang/translations/tt.po +44 -4
- package/lang/translations/ug.po +44 -4
- package/lang/translations/uk.po +44 -4
- package/lang/translations/ur.po +44 -4
- package/lang/translations/uz.po +44 -4
- package/lang/translations/vi.po +44 -4
- package/lang/translations/zh-cn.po +44 -4
- package/lang/translations/zh.po +44 -4
- package/package.json +3 -4
- package/src/button/button.d.ts +6 -0
- package/src/button/buttonview.d.ts +4 -0
- package/src/button/buttonview.js +1 -0
- package/src/button/filedialogbuttonview.d.ts +80 -0
- package/src/button/filedialogbuttonview.js +103 -0
- package/src/dropdown/utils.js +1 -5
- package/src/editorui/accessibilityhelp/accessibilityhelp.d.ts +4 -0
- package/src/editorui/accessibilityhelp/accessibilityhelp.js +25 -9
- package/src/index.d.ts +12 -3
- package/src/index.js +10 -2
- package/src/menubar/menubarmenubuttonview.d.ts +35 -0
- package/src/menubar/menubarmenubuttonview.js +64 -0
- package/src/menubar/menubarmenulistitembuttonview.d.ts +21 -0
- package/src/menubar/menubarmenulistitembuttonview.js +30 -0
- package/src/menubar/menubarmenulistitemfiledialogbuttonview.d.ts +23 -0
- package/src/menubar/menubarmenulistitemfiledialogbuttonview.js +32 -0
- package/src/menubar/menubarmenulistitemview.d.ts +25 -0
- package/src/menubar/menubarmenulistitemview.js +34 -0
- package/src/menubar/menubarmenulistview.d.ts +24 -0
- package/src/menubar/menubarmenulistview.js +23 -0
- package/src/menubar/menubarmenupanelview.d.ts +53 -0
- package/src/menubar/menubarmenupanelview.js +60 -0
- package/src/menubar/menubarmenuview.d.ts +109 -0
- package/src/menubar/menubarmenuview.js +159 -0
- package/src/menubar/menubarview.d.ts +164 -0
- package/src/menubar/menubarview.js +254 -0
- package/src/menubar/utils.d.ts +432 -0
- package/src/menubar/utils.js +1320 -0
- package/src/toolbar/toolbarview.js +5 -3
- package/src/tooltipmanager.d.ts +11 -0
- package/src/tooltipmanager.js +37 -6
- package/theme/components/menubar/menubar.css +10 -0
- package/theme/components/menubar/menubarmenu.css +9 -0
- package/theme/components/menubar/menubarmenubutton.css +11 -0
- package/theme/components/menubar/menubarmenulistitem.css +10 -0
- package/theme/components/menubar/menubarmenulistitembutton.css +10 -0
- package/theme/components/menubar/menubarmenupanel.css +62 -0
- package/theme/components/tooltip/tooltip.css +0 -3
- package/theme/globals/_reset.css +13 -0
- package/theme/globals/globals.css +1 -0
- package/dist/content-index.css +0 -4
- package/dist/editor-index.css +0 -445
- package/dist/index.css +0 -844
- package/dist/index.css.map +0 -1
- package/dist/translations/ar.d.ts +0 -8
- package/dist/translations/ar.js +0 -5
- package/dist/translations/ast.d.ts +0 -8
- package/dist/translations/ast.js +0 -5
- package/dist/translations/az.d.ts +0 -8
- package/dist/translations/az.js +0 -5
- package/dist/translations/bg.d.ts +0 -8
- package/dist/translations/bg.js +0 -5
- package/dist/translations/bn.d.ts +0 -8
- package/dist/translations/bn.js +0 -5
- package/dist/translations/ca.d.ts +0 -8
- package/dist/translations/ca.js +0 -5
- package/dist/translations/cs.d.ts +0 -8
- package/dist/translations/cs.js +0 -5
- package/dist/translations/da.d.ts +0 -8
- package/dist/translations/da.js +0 -5
- package/dist/translations/de-ch.d.ts +0 -8
- package/dist/translations/de-ch.js +0 -5
- package/dist/translations/de.d.ts +0 -8
- package/dist/translations/de.js +0 -5
- package/dist/translations/el.d.ts +0 -8
- package/dist/translations/el.js +0 -5
- package/dist/translations/en-au.d.ts +0 -8
- package/dist/translations/en-au.js +0 -5
- package/dist/translations/en-gb.d.ts +0 -8
- package/dist/translations/en-gb.js +0 -5
- package/dist/translations/en.d.ts +0 -8
- package/dist/translations/en.js +0 -5
- package/dist/translations/eo.d.ts +0 -8
- package/dist/translations/eo.js +0 -5
- package/dist/translations/es.d.ts +0 -8
- package/dist/translations/es.js +0 -5
- package/dist/translations/et.d.ts +0 -8
- package/dist/translations/et.js +0 -5
- package/dist/translations/eu.d.ts +0 -8
- package/dist/translations/eu.js +0 -5
- package/dist/translations/fa.d.ts +0 -8
- package/dist/translations/fa.js +0 -5
- package/dist/translations/fi.d.ts +0 -8
- package/dist/translations/fi.js +0 -5
- package/dist/translations/fr.d.ts +0 -8
- package/dist/translations/fr.js +0 -5
- package/dist/translations/gl.d.ts +0 -8
- package/dist/translations/gl.js +0 -5
- package/dist/translations/he.d.ts +0 -8
- package/dist/translations/he.js +0 -5
- package/dist/translations/hi.d.ts +0 -8
- package/dist/translations/hi.js +0 -5
- package/dist/translations/hr.d.ts +0 -8
- package/dist/translations/hr.js +0 -5
- package/dist/translations/hu.d.ts +0 -8
- package/dist/translations/hu.js +0 -5
- package/dist/translations/id.d.ts +0 -8
- package/dist/translations/id.js +0 -5
- package/dist/translations/it.d.ts +0 -8
- package/dist/translations/it.js +0 -5
- package/dist/translations/ja.d.ts +0 -8
- package/dist/translations/ja.js +0 -5
- package/dist/translations/km.d.ts +0 -8
- package/dist/translations/km.js +0 -5
- package/dist/translations/kn.d.ts +0 -8
- package/dist/translations/kn.js +0 -5
- package/dist/translations/ko.d.ts +0 -8
- package/dist/translations/ko.js +0 -5
- package/dist/translations/ku.d.ts +0 -8
- package/dist/translations/ku.js +0 -5
- package/dist/translations/lt.d.ts +0 -8
- package/dist/translations/lt.js +0 -5
- package/dist/translations/lv.d.ts +0 -8
- package/dist/translations/lv.js +0 -5
- package/dist/translations/ms.d.ts +0 -8
- package/dist/translations/ms.js +0 -5
- package/dist/translations/nb.d.ts +0 -8
- package/dist/translations/nb.js +0 -5
- package/dist/translations/ne.d.ts +0 -8
- package/dist/translations/ne.js +0 -5
- package/dist/translations/nl.d.ts +0 -8
- package/dist/translations/nl.js +0 -5
- package/dist/translations/no.d.ts +0 -8
- package/dist/translations/no.js +0 -5
- package/dist/translations/pl.d.ts +0 -8
- package/dist/translations/pl.js +0 -5
- package/dist/translations/pt-br.d.ts +0 -8
- package/dist/translations/pt-br.js +0 -5
- package/dist/translations/pt.d.ts +0 -8
- package/dist/translations/pt.js +0 -5
- package/dist/translations/ro.d.ts +0 -8
- package/dist/translations/ro.js +0 -5
- package/dist/translations/ru.d.ts +0 -8
- package/dist/translations/ru.js +0 -5
- package/dist/translations/sk.d.ts +0 -8
- package/dist/translations/sk.js +0 -5
- package/dist/translations/sl.d.ts +0 -8
- package/dist/translations/sl.js +0 -5
- package/dist/translations/sq.d.ts +0 -8
- package/dist/translations/sq.js +0 -5
- package/dist/translations/sr-latn.d.ts +0 -8
- package/dist/translations/sr-latn.js +0 -5
- package/dist/translations/sr.d.ts +0 -8
- package/dist/translations/sr.js +0 -5
- package/dist/translations/sv.d.ts +0 -8
- package/dist/translations/sv.js +0 -5
- package/dist/translations/th.d.ts +0 -8
- package/dist/translations/th.js +0 -5
- package/dist/translations/tk.d.ts +0 -8
- package/dist/translations/tk.js +0 -5
- package/dist/translations/tr.d.ts +0 -8
- package/dist/translations/tr.js +0 -5
- package/dist/translations/tt.d.ts +0 -8
- package/dist/translations/tt.js +0 -5
- package/dist/translations/ug.d.ts +0 -8
- package/dist/translations/ug.js +0 -5
- package/dist/translations/uk.d.ts +0 -8
- package/dist/translations/uk.js +0 -5
- package/dist/translations/ur.d.ts +0 -8
- package/dist/translations/ur.js +0 -5
- package/dist/translations/uz.d.ts +0 -8
- package/dist/translations/uz.js +0 -5
- package/dist/translations/vi.d.ts +0 -8
- package/dist/translations/vi.js +0 -5
- package/dist/translations/zh-cn.d.ts +0 -8
- package/dist/translations/zh-cn.js +0 -5
- package/dist/translations/zh.d.ts +0 -8
- package/dist/translations/zh.js +0 -5
- package/dist/types/arialiveannouncer.d.ts +0 -102
- package/dist/types/augmentation.d.ts +0 -92
- package/dist/types/autocomplete/autocompleteview.d.ts +0 -85
- package/dist/types/bindings/addkeyboardhandlingforgrid.d.ts +0 -31
- package/dist/types/bindings/clickoutsidehandler.d.ts +0 -32
- package/dist/types/bindings/csstransitiondisablermixin.d.ts +0 -44
- package/dist/types/bindings/draggableviewmixin.d.ts +0 -50
- package/dist/types/bindings/injectcsstransitiondisabler.d.ts +0 -63
- package/dist/types/bindings/preventdefault.d.ts +0 -37
- package/dist/types/bindings/submithandler.d.ts +0 -61
- package/dist/types/button/button.d.ts +0 -179
- package/dist/types/button/buttonlabel.d.ts +0 -38
- package/dist/types/button/buttonlabelview.d.ts +0 -35
- package/dist/types/button/buttonview.d.ts +0 -185
- package/dist/types/button/switchbuttonview.d.ts +0 -49
- package/dist/types/collapsible/collapsibleview.d.ts +0 -74
- package/dist/types/colorgrid/colorgridview.d.ts +0 -136
- package/dist/types/colorgrid/colortileview.d.ts +0 -32
- package/dist/types/colorgrid/utils.d.ts +0 -51
- package/dist/types/colorpicker/colorpickerview.d.ts +0 -146
- package/dist/types/colorpicker/utils.d.ts +0 -52
- package/dist/types/colorselector/colorgridsfragmentview.d.ts +0 -199
- package/dist/types/colorselector/colorpickerfragmentview.d.ts +0 -133
- package/dist/types/colorselector/colorselectorview.d.ts +0 -246
- package/dist/types/colorselector/documentcolorcollection.d.ts +0 -74
- package/dist/types/componentfactory.d.ts +0 -85
- package/dist/types/dialog/dialog.d.ts +0 -277
- package/dist/types/dialog/dialogactionsview.d.ts +0 -73
- package/dist/types/dialog/dialogcontentview.d.ts +0 -31
- package/dist/types/dialog/dialogview.d.ts +0 -260
- package/dist/types/dropdown/button/dropdownbutton.d.ts +0 -29
- package/dist/types/dropdown/button/dropdownbuttonview.d.ts +0 -52
- package/dist/types/dropdown/button/splitbuttonview.d.ts +0 -166
- package/dist/types/dropdown/dropdownpanelfocusable.d.ts +0 -25
- package/dist/types/dropdown/dropdownpanelview.d.ts +0 -66
- package/dist/types/dropdown/dropdownview.d.ts +0 -319
- package/dist/types/dropdown/utils.d.ts +0 -239
- package/dist/types/editableui/editableuiview.d.ts +0 -76
- package/dist/types/editableui/inline/inlineeditableuiview.d.ts +0 -44
- package/dist/types/editorui/accessibilityhelp/accessibilityhelp.d.ts +0 -51
- package/dist/types/editorui/accessibilityhelp/accessibilityhelpcontentview.d.ts +0 -39
- package/dist/types/editorui/bodycollection.d.ts +0 -59
- package/dist/types/editorui/boxed/boxededitoruiview.d.ts +0 -44
- package/dist/types/editorui/editorui.d.ts +0 -292
- package/dist/types/editorui/editoruiview.d.ts +0 -43
- package/dist/types/editorui/poweredby.d.ts +0 -75
- package/dist/types/focuscycler.d.ts +0 -249
- package/dist/types/formheader/formheaderview.d.ts +0 -63
- package/dist/types/highlightedtext/highlightedtextview.d.ts +0 -42
- package/dist/types/icon/iconview.d.ts +0 -92
- package/dist/types/iframe/iframeview.d.ts +0 -54
- package/dist/types/index.d.ts +0 -78
- package/dist/types/input/inputbase.d.ts +0 -123
- package/dist/types/input/inputview.d.ts +0 -40
- package/dist/types/inputnumber/inputnumberview.d.ts +0 -53
- package/dist/types/inputtext/inputtextview.d.ts +0 -22
- package/dist/types/label/labelview.d.ts +0 -40
- package/dist/types/labeledfield/labeledfieldview.d.ts +0 -191
- package/dist/types/labeledfield/utils.d.ts +0 -127
- package/dist/types/labeledinput/labeledinputview.d.ts +0 -129
- package/dist/types/list/listitemgroupview.d.ts +0 -63
- package/dist/types/list/listitemview.d.ts +0 -40
- package/dist/types/list/listseparatorview.d.ts +0 -22
- package/dist/types/list/listview.d.ts +0 -128
- package/dist/types/model.d.ts +0 -26
- package/dist/types/notification/notification.d.ts +0 -215
- package/dist/types/panel/balloon/balloonpanelview.d.ts +0 -689
- package/dist/types/panel/balloon/contextualballoon.d.ts +0 -303
- package/dist/types/panel/sticky/stickypanelview.d.ts +0 -160
- package/dist/types/search/filteredview.d.ts +0 -35
- package/dist/types/search/searchinfoview.d.ts +0 -49
- package/dist/types/search/searchresultsview.d.ts +0 -58
- package/dist/types/search/text/searchtextqueryview.d.ts +0 -80
- package/dist/types/search/text/searchtextview.d.ts +0 -223
- package/dist/types/spinner/spinnerview.d.ts +0 -29
- package/dist/types/template.d.ts +0 -946
- package/dist/types/textarea/textareaview.d.ts +0 -108
- package/dist/types/toolbar/balloon/balloontoolbar.d.ts +0 -121
- package/dist/types/toolbar/block/blockbuttonview.d.ts +0 -39
- package/dist/types/toolbar/block/blocktoolbar.d.ts +0 -157
- package/dist/types/toolbar/normalizetoolbarconfig.d.ts +0 -44
- package/dist/types/toolbar/toolbarlinebreakview.d.ts +0 -22
- package/dist/types/toolbar/toolbarseparatorview.d.ts +0 -22
- package/dist/types/toolbar/toolbarview.d.ts +0 -271
- package/dist/types/tooltipmanager.d.ts +0 -188
- package/dist/types/view.d.ts +0 -426
- package/dist/types/viewcollection.d.ts +0 -143
|
@@ -0,0 +1,1320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module ui/menubar/utils
|
|
7
|
+
*/
|
|
8
|
+
import clickOutsideHandler from '../bindings/clickoutsidehandler.js';
|
|
9
|
+
import MenuBarMenuListItemView from './menubarmenulistitemview.js';
|
|
10
|
+
import { cloneDeep } from 'lodash-es';
|
|
11
|
+
import { logWarning } from '@ckeditor/ckeditor5-utils';
|
|
12
|
+
const NESTED_PANEL_HORIZONTAL_OFFSET = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Behaviors of the {@link module:ui/menubar/menubarview~MenuBarView} component.
|
|
15
|
+
*/
|
|
16
|
+
export const MenuBarBehaviors = {
|
|
17
|
+
/**
|
|
18
|
+
* When the bar is already open:
|
|
19
|
+
* * Opens the menu when the user hovers over its button.
|
|
20
|
+
* * Closes open menu when another menu's button gets hovered.
|
|
21
|
+
*/
|
|
22
|
+
toggleMenusAndFocusItemsOnHover(menuBarView) {
|
|
23
|
+
menuBarView.on('menu:mouseenter', evt => {
|
|
24
|
+
// This works only when the menu bar has already been open and the user hover over the menu bar.
|
|
25
|
+
if (!menuBarView.isOpen) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const menuView of menuBarView.menus) {
|
|
29
|
+
// @if CK_DEBUG_MENU_BAR // const wasOpen = menuView.isOpen;
|
|
30
|
+
const pathLeaf = evt.path[0];
|
|
31
|
+
const isListItemContainingMenu = pathLeaf instanceof MenuBarMenuListItemView && pathLeaf.children.first === menuView;
|
|
32
|
+
menuView.isOpen = (evt.path.includes(menuView) || isListItemContainingMenu) && menuView.isEnabled;
|
|
33
|
+
// @if CK_DEBUG_MENU_BAR // if ( wasOpen !== menuView.isOpen ) {
|
|
34
|
+
// @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] toggleMenusAndFocusItemsOnHover(): Toggle',
|
|
35
|
+
// @if CK_DEBUG_MENU_BAR // logMenu( menuView ), 'isOpen', menuView.isOpen
|
|
36
|
+
// @if CK_DEBUG_MENU_BAR // );
|
|
37
|
+
// @if CK_DEBUG_MENU_BAR // }
|
|
38
|
+
}
|
|
39
|
+
evt.source.focus();
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* Moves between top-level menus using the arrow left and right keys.
|
|
44
|
+
*
|
|
45
|
+
* If the menubar has already been open, the arrow keys move focus between top-level menu buttons and open them.
|
|
46
|
+
* If the menubar is closed, the arrow keys only move focus between top-level menu buttons.
|
|
47
|
+
*/
|
|
48
|
+
focusCycleMenusOnArrows(menuBarView) {
|
|
49
|
+
const isContentRTL = menuBarView.locale.uiLanguageDirection === 'rtl';
|
|
50
|
+
menuBarView.on('menu:arrowright', evt => {
|
|
51
|
+
cycleTopLevelMenus(evt.source, isContentRTL ? -1 : 1);
|
|
52
|
+
});
|
|
53
|
+
menuBarView.on('menu:arrowleft', evt => {
|
|
54
|
+
cycleTopLevelMenus(evt.source, isContentRTL ? 1 : -1);
|
|
55
|
+
});
|
|
56
|
+
function cycleTopLevelMenus(currentMenuView, step) {
|
|
57
|
+
const currentIndex = menuBarView.children.getIndex(currentMenuView);
|
|
58
|
+
const isCurrentMenuViewOpen = currentMenuView.isOpen;
|
|
59
|
+
const menusCount = menuBarView.children.length;
|
|
60
|
+
const menuViewToOpen = menuBarView.children.get((currentIndex + menusCount + step) % menusCount);
|
|
61
|
+
currentMenuView.isOpen = false;
|
|
62
|
+
if (isCurrentMenuViewOpen) {
|
|
63
|
+
menuViewToOpen.isOpen = true;
|
|
64
|
+
}
|
|
65
|
+
menuViewToOpen.buttonView.focus();
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Closes the entire sub-menu structure when the bar is closed. This prevents sub-menus from being open if the user
|
|
70
|
+
* closes the entire bar, and then re-opens some top-level menu.
|
|
71
|
+
*/
|
|
72
|
+
closeMenusWhenTheBarCloses(menuBarView) {
|
|
73
|
+
menuBarView.on('change:isOpen', () => {
|
|
74
|
+
if (!menuBarView.isOpen) {
|
|
75
|
+
menuBarView.menus.forEach(menuView => {
|
|
76
|
+
menuView.isOpen = false;
|
|
77
|
+
// @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenusWhenTheBarCloses(): Closing', logMenu( menuView ) );
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* Handles the following case:
|
|
84
|
+
* 1. Hover to open a sub-menu (A). The button has focus.
|
|
85
|
+
* 2. Press arrow up/down to move focus to another sub-menu (B) button.
|
|
86
|
+
* 3. Press arrow right to open the sub-menu (B).
|
|
87
|
+
* 4. The sub-menu (A) should close as it would with `toggleMenusAndFocusItemsOnHover()`.
|
|
88
|
+
*/
|
|
89
|
+
closeMenuWhenAnotherOnTheSameLevelOpens(menuBarView) {
|
|
90
|
+
menuBarView.on('menu:change:isOpen', (evt, name, isOpen) => {
|
|
91
|
+
if (isOpen) {
|
|
92
|
+
menuBarView.menus
|
|
93
|
+
.filter(menuView => {
|
|
94
|
+
return evt.source.parentMenuView === menuView.parentMenuView &&
|
|
95
|
+
evt.source !== menuView &&
|
|
96
|
+
menuView.isOpen;
|
|
97
|
+
}).forEach(menuView => {
|
|
98
|
+
menuView.isOpen = false;
|
|
99
|
+
// @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeMenuWhenAnotherOpens(): Closing', logMenu( menuView ) );
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Closes the bar when the user clicked outside of it (page body, editor root, etc.).
|
|
106
|
+
*/
|
|
107
|
+
closeOnClickOutside(menuBarView) {
|
|
108
|
+
clickOutsideHandler({
|
|
109
|
+
emitter: menuBarView,
|
|
110
|
+
activator: () => menuBarView.isOpen,
|
|
111
|
+
callback: () => menuBarView.close(),
|
|
112
|
+
contextElements: () => menuBarView.children.map(child => child.element)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Behaviors of the {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} component.
|
|
118
|
+
*/
|
|
119
|
+
export const MenuBarMenuBehaviors = {
|
|
120
|
+
/**
|
|
121
|
+
* If the button of the menu is focused, pressing the arrow down key should open the panel and focus it.
|
|
122
|
+
* This is analogous to the {@link module:ui/dropdown/dropdownview~DropdownView}.
|
|
123
|
+
*/
|
|
124
|
+
openAndFocusPanelOnArrowDownKey(menuView) {
|
|
125
|
+
menuView.keystrokes.set('arrowdown', (data, cancel) => {
|
|
126
|
+
if (menuView.focusTracker.focusedElement === menuView.buttonView.element) {
|
|
127
|
+
if (!menuView.isOpen) {
|
|
128
|
+
menuView.isOpen = true;
|
|
129
|
+
}
|
|
130
|
+
menuView.panelView.focus();
|
|
131
|
+
cancel();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Open the menu on the right arrow key press. This allows for navigating to sub-menus using the keyboard.
|
|
137
|
+
*/
|
|
138
|
+
openOnArrowRightKey(menuView) {
|
|
139
|
+
const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowleft' : 'arrowright';
|
|
140
|
+
menuView.keystrokes.set(keystroke, (data, cancel) => {
|
|
141
|
+
if (menuView.focusTracker.focusedElement !== menuView.buttonView.element || !menuView.isEnabled) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] openOnArrowRightKey(): Opening', logMenu( menuView ) );
|
|
145
|
+
if (!menuView.isOpen) {
|
|
146
|
+
menuView.isOpen = true;
|
|
147
|
+
}
|
|
148
|
+
menuView.panelView.focus();
|
|
149
|
+
cancel();
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
/**
|
|
153
|
+
* Opens the menu on its button click. Note that this behavior only opens but never closes the menu (unlike
|
|
154
|
+
* {@link module:ui/dropdown/dropdownview~DropdownView}).
|
|
155
|
+
*/
|
|
156
|
+
openOnButtonClick(menuView) {
|
|
157
|
+
menuView.buttonView.on('execute', () => {
|
|
158
|
+
menuView.isOpen = true;
|
|
159
|
+
menuView.panelView.focus();
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
/**
|
|
163
|
+
* Toggles the menu on its button click. This behavior is analogous to {@link module:ui/dropdown/dropdownview~DropdownView}.
|
|
164
|
+
*/
|
|
165
|
+
toggleOnButtonClick(menuView) {
|
|
166
|
+
menuView.buttonView.on('execute', () => {
|
|
167
|
+
menuView.isOpen = !menuView.isOpen;
|
|
168
|
+
if (menuView.isOpen) {
|
|
169
|
+
menuView.panelView.focus();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Closes the menu on the right left key press. This allows for navigating to sub-menus using the keyboard.
|
|
175
|
+
*/
|
|
176
|
+
closeOnArrowLeftKey(menuView) {
|
|
177
|
+
const keystroke = menuView.locale.uiLanguageDirection === 'rtl' ? 'arrowright' : 'arrowleft';
|
|
178
|
+
menuView.keystrokes.set(keystroke, (data, cancel) => {
|
|
179
|
+
if (menuView.isOpen) {
|
|
180
|
+
menuView.isOpen = false;
|
|
181
|
+
menuView.focus();
|
|
182
|
+
cancel();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* Closes the menu on the esc key press. This allows for navigating to sub-menus using the keyboard.
|
|
188
|
+
*/
|
|
189
|
+
closeOnEscKey(menuView) {
|
|
190
|
+
menuView.keystrokes.set('esc', (data, cancel) => {
|
|
191
|
+
if (menuView.isOpen) {
|
|
192
|
+
menuView.isOpen = false;
|
|
193
|
+
menuView.focus();
|
|
194
|
+
cancel();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* Closes the menu when its parent menu also closed. This prevents from orphaned open menus when the parent menu re-opens.
|
|
200
|
+
*/
|
|
201
|
+
closeOnParentClose(menuView) {
|
|
202
|
+
menuView.parentMenuView.on('change:isOpen', (evt, name, isOpen) => {
|
|
203
|
+
if (!isOpen && evt.source === menuView.parentMenuView) {
|
|
204
|
+
// @if CK_DEBUG_MENU_BAR // console.log( '[BEHAVIOR] closeOnParentClose(): Closing', logMenu( menuView ) );
|
|
205
|
+
menuView.isOpen = false;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
// @if CK_DEBUG_MENU_BAR // function logMenu( menuView: MenuBarMenuView ) {
|
|
211
|
+
// @if CK_DEBUG_MENU_BAR // return `"${ menuView.buttonView.label }"`;
|
|
212
|
+
// @if CK_DEBUG_MENU_BAR // }
|
|
213
|
+
/**
|
|
214
|
+
* Contains every positioning function used by {@link module:ui/menubar/menubarmenuview~MenuBarMenuView} that decides where the
|
|
215
|
+
* {@link module:ui/menubar/menubarmenuview~MenuBarMenuView#panelView} should be placed.
|
|
216
|
+
*
|
|
217
|
+
* Top-level menu positioning functions:
|
|
218
|
+
*
|
|
219
|
+
* ┌──────┐
|
|
220
|
+
* │ │
|
|
221
|
+
* ├──────┴────────┐
|
|
222
|
+
* │ │
|
|
223
|
+
* │ │
|
|
224
|
+
* │ │
|
|
225
|
+
* │ SE │
|
|
226
|
+
* └───────────────┘
|
|
227
|
+
*
|
|
228
|
+
* ┌──────┐
|
|
229
|
+
* │ │
|
|
230
|
+
* ┌────────┴──────┤
|
|
231
|
+
* │ │
|
|
232
|
+
* │ │
|
|
233
|
+
* │ │
|
|
234
|
+
* │ SW │
|
|
235
|
+
* └───────────────┘
|
|
236
|
+
*
|
|
237
|
+
* ┌───────────────┐
|
|
238
|
+
* │ NW │
|
|
239
|
+
* │ │
|
|
240
|
+
* │ │
|
|
241
|
+
* │ │
|
|
242
|
+
* └────────┬──────┤
|
|
243
|
+
* │ │
|
|
244
|
+
* └──────┘
|
|
245
|
+
*
|
|
246
|
+
* ┌───────────────┐
|
|
247
|
+
* │ NE │
|
|
248
|
+
* │ │
|
|
249
|
+
* │ │
|
|
250
|
+
* │ │
|
|
251
|
+
* ├──────┬────────┘
|
|
252
|
+
* │ │
|
|
253
|
+
* └──────┘
|
|
254
|
+
*
|
|
255
|
+
* Sub-menu positioning functions:
|
|
256
|
+
*
|
|
257
|
+
* ┌──────┬───────────────┐
|
|
258
|
+
* │ │ │
|
|
259
|
+
* └──────┤ │
|
|
260
|
+
* │ │
|
|
261
|
+
* │ ES │
|
|
262
|
+
* └───────────────┘
|
|
263
|
+
*
|
|
264
|
+
* ┌───────────────┬──────┐
|
|
265
|
+
* │ │ │
|
|
266
|
+
* │ ├──────┘
|
|
267
|
+
* │ │
|
|
268
|
+
* │ WS │
|
|
269
|
+
* └───────────────┘
|
|
270
|
+
*
|
|
271
|
+
* ┌───────────────┐
|
|
272
|
+
* │ EN │
|
|
273
|
+
* │ │
|
|
274
|
+
* ┌──────┤ │
|
|
275
|
+
* │ │ │
|
|
276
|
+
* └──────┴───────────────┘
|
|
277
|
+
*
|
|
278
|
+
* ┌───────────────┐
|
|
279
|
+
* │ WN │
|
|
280
|
+
* │ │
|
|
281
|
+
* │ ├──────┐
|
|
282
|
+
* │ │ │
|
|
283
|
+
* └───────────────┴──────┘
|
|
284
|
+
*/
|
|
285
|
+
export const MenuBarMenuViewPanelPositioningFunctions = {
|
|
286
|
+
southEast: buttonRect => {
|
|
287
|
+
return {
|
|
288
|
+
top: buttonRect.bottom,
|
|
289
|
+
left: buttonRect.left,
|
|
290
|
+
name: 'se'
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
southWest: (buttonRect, panelRect) => {
|
|
294
|
+
return {
|
|
295
|
+
top: buttonRect.bottom,
|
|
296
|
+
left: buttonRect.left - panelRect.width + buttonRect.width,
|
|
297
|
+
name: 'sw'
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
northEast: (buttonRect, panelRect) => {
|
|
301
|
+
return {
|
|
302
|
+
top: buttonRect.top - panelRect.height,
|
|
303
|
+
left: buttonRect.left,
|
|
304
|
+
name: 'ne'
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
northWest: (buttonRect, panelRect) => {
|
|
308
|
+
return {
|
|
309
|
+
top: buttonRect.top - panelRect.height,
|
|
310
|
+
left: buttonRect.left - panelRect.width + buttonRect.width,
|
|
311
|
+
name: 'nw'
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
eastSouth: buttonRect => {
|
|
315
|
+
return {
|
|
316
|
+
top: buttonRect.top,
|
|
317
|
+
left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,
|
|
318
|
+
name: 'es'
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
eastNorth: (buttonRect, panelRect) => {
|
|
322
|
+
return {
|
|
323
|
+
top: buttonRect.top - panelRect.height,
|
|
324
|
+
left: buttonRect.right - NESTED_PANEL_HORIZONTAL_OFFSET,
|
|
325
|
+
name: 'en'
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
westSouth: (buttonRect, panelRect) => {
|
|
329
|
+
return {
|
|
330
|
+
top: buttonRect.top,
|
|
331
|
+
left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,
|
|
332
|
+
name: 'ws'
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
westNorth: (buttonRect, panelRect) => {
|
|
336
|
+
return {
|
|
337
|
+
top: buttonRect.top - panelRect.height,
|
|
338
|
+
left: buttonRect.left - panelRect.width + NESTED_PANEL_HORIZONTAL_OFFSET,
|
|
339
|
+
name: 'wn'
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* The default items {@link module:core/editor/editorconfig~EditorConfig#menuBar configuration} of the
|
|
345
|
+
* {@link module:ui/menubar/menubarview~MenuBarView} component. It contains names of all menu bar components
|
|
346
|
+
* registered in the {@link module:ui/componentfactory~ComponentFactory component factory} (available in the project).
|
|
347
|
+
*
|
|
348
|
+
* **Note**: Menu bar component names provided by core editor features are prefixed with `menuBar:` in order to distinguish
|
|
349
|
+
* them from components referenced by the {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}, for instance,
|
|
350
|
+
* `'menuBar:bold'` is a menu bar button but `'bold'` is a toolbar button.
|
|
351
|
+
*
|
|
352
|
+
* Below is the preset menu bar structure (the default value of `config.menuBar.items` property):
|
|
353
|
+
*
|
|
354
|
+
* ```ts
|
|
355
|
+
* [
|
|
356
|
+
* {
|
|
357
|
+
* menuId: 'file',
|
|
358
|
+
* label: 'File',
|
|
359
|
+
* groups: [
|
|
360
|
+
* {
|
|
361
|
+
* groupId: 'export',
|
|
362
|
+
* items: [
|
|
363
|
+
* 'menuBar:exportPdf',
|
|
364
|
+
* 'menuBar:exportWord'
|
|
365
|
+
* ]
|
|
366
|
+
* },
|
|
367
|
+
* {
|
|
368
|
+
* groupId: 'import',
|
|
369
|
+
* items: [
|
|
370
|
+
* 'menuBar:importWord'
|
|
371
|
+
* ]
|
|
372
|
+
* },
|
|
373
|
+
* {
|
|
374
|
+
* groupId: 'revisionHistory',
|
|
375
|
+
* items: [
|
|
376
|
+
* 'menuBar:revisionHistory'
|
|
377
|
+
* ]
|
|
378
|
+
* }
|
|
379
|
+
* ]
|
|
380
|
+
* },
|
|
381
|
+
* {
|
|
382
|
+
* menuId: 'edit',
|
|
383
|
+
* label: 'Edit',
|
|
384
|
+
* groups: [
|
|
385
|
+
* {
|
|
386
|
+
* groupId: 'undo',
|
|
387
|
+
* items: [
|
|
388
|
+
* 'menuBar:undo',
|
|
389
|
+
* 'menuBar:redo'
|
|
390
|
+
* ]
|
|
391
|
+
* },
|
|
392
|
+
* {
|
|
393
|
+
* groupId: 'selectAll',
|
|
394
|
+
* items: [
|
|
395
|
+
* 'menuBar:selectAll'
|
|
396
|
+
* ]
|
|
397
|
+
* },
|
|
398
|
+
* {
|
|
399
|
+
* groupId: 'findAndReplace',
|
|
400
|
+
* items: [
|
|
401
|
+
* 'menuBar:findAndReplace'
|
|
402
|
+
* ]
|
|
403
|
+
* }
|
|
404
|
+
* ]
|
|
405
|
+
* },
|
|
406
|
+
* {
|
|
407
|
+
* menuId: 'view',
|
|
408
|
+
* label: 'View',
|
|
409
|
+
* groups: [
|
|
410
|
+
* {
|
|
411
|
+
* groupId: 'sourceEditing',
|
|
412
|
+
* items: [
|
|
413
|
+
* 'menuBar:sourceEditing'
|
|
414
|
+
* ]
|
|
415
|
+
* },
|
|
416
|
+
* {
|
|
417
|
+
* groupId: 'showBlocks',
|
|
418
|
+
* items: [
|
|
419
|
+
* 'menuBar:showBlocks'
|
|
420
|
+
* ]
|
|
421
|
+
* },
|
|
422
|
+
* {
|
|
423
|
+
* groupId: 'restrictedEditingException',
|
|
424
|
+
* items: [
|
|
425
|
+
* 'menuBar:restrictedEditingException'
|
|
426
|
+
* ]
|
|
427
|
+
* }
|
|
428
|
+
* ]
|
|
429
|
+
* },
|
|
430
|
+
* {
|
|
431
|
+
* menuId: 'insert',
|
|
432
|
+
* label: 'Insert',
|
|
433
|
+
* groups: [
|
|
434
|
+
* {
|
|
435
|
+
* groupId: 'insertMainWidgets',
|
|
436
|
+
* items: [
|
|
437
|
+
* 'menuBar:uploadImage',
|
|
438
|
+
* 'menuBar:ckbox',
|
|
439
|
+
* 'menuBar:ckfinder',
|
|
440
|
+
* 'menuBar:insertTable'
|
|
441
|
+
* ]
|
|
442
|
+
* },
|
|
443
|
+
* {
|
|
444
|
+
* groupId: 'insertInline',
|
|
445
|
+
* items: [
|
|
446
|
+
* 'menuBar:link',
|
|
447
|
+
* 'menuBar:comment'
|
|
448
|
+
* ]
|
|
449
|
+
* },
|
|
450
|
+
* {
|
|
451
|
+
* groupId: 'insertMinorWidgets',
|
|
452
|
+
* items: [
|
|
453
|
+
* 'menuBar:insertTemplate',
|
|
454
|
+
* 'menuBar:blockQuote',
|
|
455
|
+
* 'menuBar:codeBlock',
|
|
456
|
+
* 'menuBar:htmlEmbed'
|
|
457
|
+
* ]
|
|
458
|
+
* },
|
|
459
|
+
* {
|
|
460
|
+
* groupId: 'insertStructureWidgets',
|
|
461
|
+
* items: [
|
|
462
|
+
* 'menuBar:horizontalLine',
|
|
463
|
+
* 'menuBar:pageBreak',
|
|
464
|
+
* 'menuBar:tableOfContents'
|
|
465
|
+
* ]
|
|
466
|
+
* },
|
|
467
|
+
* {
|
|
468
|
+
* groupId: 'restrictedEditing',
|
|
469
|
+
* items: [
|
|
470
|
+
* 'menuBar:restrictedEditing'
|
|
471
|
+
* ]
|
|
472
|
+
* }
|
|
473
|
+
* ]
|
|
474
|
+
* },
|
|
475
|
+
* {
|
|
476
|
+
* menuId: 'format',
|
|
477
|
+
* label: 'Format',
|
|
478
|
+
* groups: [
|
|
479
|
+
* {
|
|
480
|
+
* groupId: 'textAndFont',
|
|
481
|
+
* items: [
|
|
482
|
+
* {
|
|
483
|
+
* menuId: 'text',
|
|
484
|
+
* label: 'Text',
|
|
485
|
+
* groups: [
|
|
486
|
+
* {
|
|
487
|
+
* groupId: 'basicStyles',
|
|
488
|
+
* items: [
|
|
489
|
+
* 'menuBar:bold',
|
|
490
|
+
* 'menuBar:italic',
|
|
491
|
+
* 'menuBar:underline',
|
|
492
|
+
* 'menuBar:strikethrough',
|
|
493
|
+
* 'menuBar:superscript',
|
|
494
|
+
* 'menuBar:subscript',
|
|
495
|
+
* 'menuBar:code'
|
|
496
|
+
* ]
|
|
497
|
+
* },
|
|
498
|
+
* {
|
|
499
|
+
* groupId: 'textPartLanguage',
|
|
500
|
+
* items: [
|
|
501
|
+
* 'menuBar:textPartLanguage'
|
|
502
|
+
* ]
|
|
503
|
+
* }
|
|
504
|
+
* ]
|
|
505
|
+
* },
|
|
506
|
+
* {
|
|
507
|
+
* menuId: 'font',
|
|
508
|
+
* label: 'Font',
|
|
509
|
+
* groups: [
|
|
510
|
+
* {
|
|
511
|
+
* groupId: 'fontProperties',
|
|
512
|
+
* items: [
|
|
513
|
+
* 'menuBar:fontSize',
|
|
514
|
+
* 'menuBar:fontFamily'
|
|
515
|
+
* ]
|
|
516
|
+
* },
|
|
517
|
+
* {
|
|
518
|
+
* groupId: 'fontColors',
|
|
519
|
+
* items: [
|
|
520
|
+
* 'menuBar:fontColor',
|
|
521
|
+
* 'menuBar:fontBackgroundColor'
|
|
522
|
+
* ]
|
|
523
|
+
* },
|
|
524
|
+
* {
|
|
525
|
+
* groupId: 'highlight',
|
|
526
|
+
* items: [
|
|
527
|
+
* 'menuBar:highlight'
|
|
528
|
+
* ]
|
|
529
|
+
* }
|
|
530
|
+
* ]
|
|
531
|
+
* },
|
|
532
|
+
* 'menuBar:heading'
|
|
533
|
+
* ]
|
|
534
|
+
* },
|
|
535
|
+
* {
|
|
536
|
+
* groupId: 'list',
|
|
537
|
+
* items: [
|
|
538
|
+
* 'menuBar:bulletedList',
|
|
539
|
+
* 'menuBar:numberedList',
|
|
540
|
+
* 'menuBar:todoList'
|
|
541
|
+
* ]
|
|
542
|
+
* },
|
|
543
|
+
* {
|
|
544
|
+
* groupId: 'indent',
|
|
545
|
+
* items: [
|
|
546
|
+
* 'menuBar:alignment',
|
|
547
|
+
* 'menuBar:indent',
|
|
548
|
+
* 'menuBar:outdent'
|
|
549
|
+
* ]
|
|
550
|
+
* },
|
|
551
|
+
* {
|
|
552
|
+
* groupId: 'caseChange',
|
|
553
|
+
* items: [
|
|
554
|
+
* 'menuBar:caseChange'
|
|
555
|
+
* ]
|
|
556
|
+
* },
|
|
557
|
+
* {
|
|
558
|
+
* groupId: 'removeFormat',
|
|
559
|
+
* items: [
|
|
560
|
+
* 'menuBar:removeFormat'
|
|
561
|
+
* ]
|
|
562
|
+
* }
|
|
563
|
+
* ]
|
|
564
|
+
* },
|
|
565
|
+
* {
|
|
566
|
+
* menuId: 'tools',
|
|
567
|
+
* label: 'Tools',
|
|
568
|
+
* groups: [
|
|
569
|
+
* {
|
|
570
|
+
* groupId: 'aiTools',
|
|
571
|
+
* items: [
|
|
572
|
+
* 'menuBar:aiAssistant',
|
|
573
|
+
* 'menuBar:aiCommands'
|
|
574
|
+
* ]
|
|
575
|
+
* },
|
|
576
|
+
* {
|
|
577
|
+
* groupId: 'tools',
|
|
578
|
+
* items: [
|
|
579
|
+
* 'menuBar:trackChanges',
|
|
580
|
+
* 'menuBar:commentsArchive'
|
|
581
|
+
* ]
|
|
582
|
+
* }
|
|
583
|
+
* ]
|
|
584
|
+
* },
|
|
585
|
+
* {
|
|
586
|
+
* menuId: 'help',
|
|
587
|
+
* label: 'Help',
|
|
588
|
+
* groups: [
|
|
589
|
+
* {
|
|
590
|
+
* groupId: 'help',
|
|
591
|
+
* items: [
|
|
592
|
+
* 'menuBar:accessibilityHelp'
|
|
593
|
+
* ]
|
|
594
|
+
* }
|
|
595
|
+
* ]
|
|
596
|
+
* }
|
|
597
|
+
* ];
|
|
598
|
+
* ```
|
|
599
|
+
*
|
|
600
|
+
* The menu bar can be customized using the `config.menuBar.removeItems` and `config.menuBar.addItems` properties.
|
|
601
|
+
*/
|
|
602
|
+
// **NOTE: Whenever you make changes to this value, reflect it in the documentation above!**
|
|
603
|
+
export const DefaultMenuBarItems = [
|
|
604
|
+
{
|
|
605
|
+
menuId: 'file',
|
|
606
|
+
label: 'File',
|
|
607
|
+
groups: [
|
|
608
|
+
{
|
|
609
|
+
groupId: 'export',
|
|
610
|
+
items: [
|
|
611
|
+
'menuBar:exportPdf',
|
|
612
|
+
'menuBar:exportWord'
|
|
613
|
+
]
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
groupId: 'import',
|
|
617
|
+
items: [
|
|
618
|
+
'menuBar:importWord'
|
|
619
|
+
]
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
groupId: 'revisionHistory',
|
|
623
|
+
items: [
|
|
624
|
+
'menuBar:revisionHistory'
|
|
625
|
+
]
|
|
626
|
+
}
|
|
627
|
+
]
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
menuId: 'edit',
|
|
631
|
+
label: 'Edit',
|
|
632
|
+
groups: [
|
|
633
|
+
{
|
|
634
|
+
groupId: 'undo',
|
|
635
|
+
items: [
|
|
636
|
+
'menuBar:undo',
|
|
637
|
+
'menuBar:redo'
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
groupId: 'selectAll',
|
|
642
|
+
items: [
|
|
643
|
+
'menuBar:selectAll'
|
|
644
|
+
]
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
groupId: 'findAndReplace',
|
|
648
|
+
items: [
|
|
649
|
+
'menuBar:findAndReplace'
|
|
650
|
+
]
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
menuId: 'view',
|
|
656
|
+
label: 'View',
|
|
657
|
+
groups: [
|
|
658
|
+
{
|
|
659
|
+
groupId: 'sourceEditing',
|
|
660
|
+
items: [
|
|
661
|
+
'menuBar:sourceEditing'
|
|
662
|
+
]
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
groupId: 'showBlocks',
|
|
666
|
+
items: [
|
|
667
|
+
'menuBar:showBlocks'
|
|
668
|
+
]
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
groupId: 'restrictedEditingException',
|
|
672
|
+
items: [
|
|
673
|
+
'menuBar:restrictedEditingException'
|
|
674
|
+
]
|
|
675
|
+
}
|
|
676
|
+
]
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
menuId: 'insert',
|
|
680
|
+
label: 'Insert',
|
|
681
|
+
groups: [
|
|
682
|
+
{
|
|
683
|
+
groupId: 'insertMainWidgets',
|
|
684
|
+
items: [
|
|
685
|
+
'menuBar:uploadImage',
|
|
686
|
+
'menuBar:ckbox',
|
|
687
|
+
'menuBar:ckfinder',
|
|
688
|
+
'menuBar:insertTable'
|
|
689
|
+
]
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
groupId: 'insertInline',
|
|
693
|
+
items: [
|
|
694
|
+
'menuBar:link',
|
|
695
|
+
'menuBar:comment'
|
|
696
|
+
]
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
groupId: 'insertMinorWidgets',
|
|
700
|
+
items: [
|
|
701
|
+
'menuBar:insertTemplate',
|
|
702
|
+
'menuBar:blockQuote',
|
|
703
|
+
'menuBar:codeBlock',
|
|
704
|
+
'menuBar:htmlEmbed'
|
|
705
|
+
]
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
groupId: 'insertStructureWidgets',
|
|
709
|
+
items: [
|
|
710
|
+
'menuBar:horizontalLine',
|
|
711
|
+
'menuBar:pageBreak',
|
|
712
|
+
'menuBar:tableOfContents'
|
|
713
|
+
]
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
groupId: 'restrictedEditing',
|
|
717
|
+
items: [
|
|
718
|
+
'menuBar:restrictedEditing'
|
|
719
|
+
]
|
|
720
|
+
}
|
|
721
|
+
]
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
menuId: 'format',
|
|
725
|
+
label: 'Format',
|
|
726
|
+
groups: [
|
|
727
|
+
{
|
|
728
|
+
groupId: 'textAndFont',
|
|
729
|
+
items: [
|
|
730
|
+
{
|
|
731
|
+
menuId: 'text',
|
|
732
|
+
label: 'Text',
|
|
733
|
+
groups: [
|
|
734
|
+
{
|
|
735
|
+
groupId: 'basicStyles',
|
|
736
|
+
items: [
|
|
737
|
+
'menuBar:bold',
|
|
738
|
+
'menuBar:italic',
|
|
739
|
+
'menuBar:underline',
|
|
740
|
+
'menuBar:strikethrough',
|
|
741
|
+
'menuBar:superscript',
|
|
742
|
+
'menuBar:subscript',
|
|
743
|
+
'menuBar:code'
|
|
744
|
+
]
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
groupId: 'textPartLanguage',
|
|
748
|
+
items: [
|
|
749
|
+
'menuBar:textPartLanguage'
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
]
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
menuId: 'font',
|
|
756
|
+
label: 'Font',
|
|
757
|
+
groups: [
|
|
758
|
+
{
|
|
759
|
+
groupId: 'fontProperties',
|
|
760
|
+
items: [
|
|
761
|
+
'menuBar:fontSize',
|
|
762
|
+
'menuBar:fontFamily'
|
|
763
|
+
]
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
groupId: 'fontColors',
|
|
767
|
+
items: [
|
|
768
|
+
'menuBar:fontColor',
|
|
769
|
+
'menuBar:fontBackgroundColor'
|
|
770
|
+
]
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
groupId: 'highlight',
|
|
774
|
+
items: [
|
|
775
|
+
'menuBar:highlight'
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
]
|
|
779
|
+
},
|
|
780
|
+
'menuBar:heading'
|
|
781
|
+
]
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
groupId: 'list',
|
|
785
|
+
items: [
|
|
786
|
+
'menuBar:bulletedList',
|
|
787
|
+
'menuBar:numberedList',
|
|
788
|
+
'menuBar:todoList'
|
|
789
|
+
]
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
groupId: 'indent',
|
|
793
|
+
items: [
|
|
794
|
+
'menuBar:alignment',
|
|
795
|
+
'menuBar:indent',
|
|
796
|
+
'menuBar:outdent'
|
|
797
|
+
]
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
groupId: 'caseChange',
|
|
801
|
+
items: [
|
|
802
|
+
'menuBar:caseChange'
|
|
803
|
+
]
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
groupId: 'removeFormat',
|
|
807
|
+
items: [
|
|
808
|
+
'menuBar:removeFormat'
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
]
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
menuId: 'tools',
|
|
815
|
+
label: 'Tools',
|
|
816
|
+
groups: [
|
|
817
|
+
{
|
|
818
|
+
groupId: 'aiTools',
|
|
819
|
+
items: [
|
|
820
|
+
'menuBar:aiAssistant',
|
|
821
|
+
'menuBar:aiCommands'
|
|
822
|
+
]
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
groupId: 'tools',
|
|
826
|
+
items: [
|
|
827
|
+
'menuBar:trackChanges',
|
|
828
|
+
'menuBar:commentsArchive'
|
|
829
|
+
]
|
|
830
|
+
}
|
|
831
|
+
]
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
menuId: 'help',
|
|
835
|
+
label: 'Help',
|
|
836
|
+
groups: [
|
|
837
|
+
{
|
|
838
|
+
groupId: 'help',
|
|
839
|
+
items: [
|
|
840
|
+
'menuBar:accessibilityHelp'
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
]
|
|
844
|
+
}
|
|
845
|
+
];
|
|
846
|
+
/**
|
|
847
|
+
* Performs a cleanup and normalization of the menu bar configuration.
|
|
848
|
+
*/
|
|
849
|
+
export function normalizeMenuBarConfig(config) {
|
|
850
|
+
let configObject;
|
|
851
|
+
// The integrator specified the config as an object but without items. Let's give them defaults but respect their
|
|
852
|
+
// additions and removals.
|
|
853
|
+
if (!('items' in config) || !config.items) {
|
|
854
|
+
configObject = {
|
|
855
|
+
items: cloneDeep(DefaultMenuBarItems),
|
|
856
|
+
addItems: [],
|
|
857
|
+
removeItems: [],
|
|
858
|
+
isVisible: true,
|
|
859
|
+
isUsingDefaultConfig: true,
|
|
860
|
+
...config
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
// The integrator specified the config as an object and there are items there. Let's take it as it is.
|
|
864
|
+
else {
|
|
865
|
+
configObject = {
|
|
866
|
+
items: config.items,
|
|
867
|
+
removeItems: [],
|
|
868
|
+
addItems: [],
|
|
869
|
+
isVisible: true,
|
|
870
|
+
isUsingDefaultConfig: false,
|
|
871
|
+
...config
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return configObject;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Processes a normalized menu bar config and returns a config clone with the following modifications:
|
|
878
|
+
*
|
|
879
|
+
* * Removed components that are not available in the component factory,
|
|
880
|
+
* * Removed obsolete separators,
|
|
881
|
+
* * Purged empty menus,
|
|
882
|
+
* * Localized top-level menu labels.
|
|
883
|
+
*/
|
|
884
|
+
export function processMenuBarConfig({ normalizedConfig, locale, componentFactory }) {
|
|
885
|
+
const configClone = cloneDeep(normalizedConfig);
|
|
886
|
+
handleRemovals(normalizedConfig, configClone);
|
|
887
|
+
handleAdditions(normalizedConfig, configClone);
|
|
888
|
+
purgeUnavailableComponents(normalizedConfig, configClone, componentFactory);
|
|
889
|
+
purgeEmptyMenus(normalizedConfig, configClone);
|
|
890
|
+
localizeMenuLabels(configClone, locale);
|
|
891
|
+
return configClone;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Removes items from the menu bar config based on user `removeItems` configuration. Users can remove
|
|
895
|
+
* individual items, groups, or entire menus. For each removed item, a warning is logged if the item
|
|
896
|
+
* was not found in the configuration.
|
|
897
|
+
*/
|
|
898
|
+
function handleRemovals(originalConfig, config) {
|
|
899
|
+
const itemsToBeRemoved = config.removeItems;
|
|
900
|
+
const successfullyRemovedItems = [];
|
|
901
|
+
// Remove top-level menus.
|
|
902
|
+
config.items = config.items.filter(({ menuId }) => {
|
|
903
|
+
if (itemsToBeRemoved.includes(menuId)) {
|
|
904
|
+
successfullyRemovedItems.push(menuId);
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
return true;
|
|
908
|
+
});
|
|
909
|
+
walkConfigMenus(config.items, menuDefinition => {
|
|
910
|
+
// Remove groups from menus.
|
|
911
|
+
menuDefinition.groups = menuDefinition.groups.filter(({ groupId }) => {
|
|
912
|
+
if (itemsToBeRemoved.includes(groupId)) {
|
|
913
|
+
successfullyRemovedItems.push(groupId);
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
return true;
|
|
917
|
+
});
|
|
918
|
+
// Remove sub-menus and items from groups.
|
|
919
|
+
for (const groupDefinition of menuDefinition.groups) {
|
|
920
|
+
groupDefinition.items = groupDefinition.items.filter(item => {
|
|
921
|
+
const itemId = getIdFromGroupItem(item);
|
|
922
|
+
if (itemsToBeRemoved.includes(itemId)) {
|
|
923
|
+
successfullyRemovedItems.push(itemId);
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
for (const itemName of itemsToBeRemoved) {
|
|
931
|
+
if (!successfullyRemovedItems.includes(itemName)) {
|
|
932
|
+
/**
|
|
933
|
+
* There was a problem processing the configuration of the menu bar. The item with the given
|
|
934
|
+
* name does could not be removed from the menu bar configuration.
|
|
935
|
+
*
|
|
936
|
+
* This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
|
|
937
|
+
* to provide a menu bar item has not been loaded or there is a typo in the
|
|
938
|
+
* {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
|
|
939
|
+
*
|
|
940
|
+
* @error menu-bar-item-could-not-be-removed
|
|
941
|
+
* @param menuBarConfig The full configuration of the menu bar.
|
|
942
|
+
* @param itemName The name of the item that was not removed from the menu bar.
|
|
943
|
+
*/
|
|
944
|
+
logWarning('menu-bar-item-could-not-be-removed', {
|
|
945
|
+
menuBarConfig: originalConfig,
|
|
946
|
+
itemName
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Handles the `config.menuBar.addItems` configuration. It allows for adding menus, groups, and items at arbitrary
|
|
953
|
+
* positions in the menu bar. If the position does not exist, a warning is logged.
|
|
954
|
+
*/
|
|
955
|
+
function handleAdditions(originalConfig, config) {
|
|
956
|
+
const itemsToBeAdded = config.addItems;
|
|
957
|
+
const successFullyAddedItems = [];
|
|
958
|
+
for (const itemToAdd of itemsToBeAdded) {
|
|
959
|
+
const relation = getRelationFromPosition(itemToAdd.position);
|
|
960
|
+
const relativeId = getRelativeIdFromPosition(itemToAdd.position);
|
|
961
|
+
// Adding a menu.
|
|
962
|
+
if (isMenuBarMenuAddition(itemToAdd)) {
|
|
963
|
+
if (!relativeId) {
|
|
964
|
+
// Adding a top-level menu at the beginning of the menu bar.
|
|
965
|
+
if (relation === 'start') {
|
|
966
|
+
config.items.unshift(itemToAdd.menu);
|
|
967
|
+
successFullyAddedItems.push(itemToAdd);
|
|
968
|
+
}
|
|
969
|
+
// Adding a top-level menu at the end of the menu bar.
|
|
970
|
+
else if (relation === 'end') {
|
|
971
|
+
config.items.push(itemToAdd.menu);
|
|
972
|
+
successFullyAddedItems.push(itemToAdd);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
const topLevelMenuDefinitionIndex = config.items.findIndex(menuDefinition => menuDefinition.menuId === relativeId);
|
|
977
|
+
// Adding a top-level menu somewhere between existing menu bar menus.
|
|
978
|
+
if (topLevelMenuDefinitionIndex != -1) {
|
|
979
|
+
if (relation === 'before') {
|
|
980
|
+
config.items.splice(topLevelMenuDefinitionIndex, 0, itemToAdd.menu);
|
|
981
|
+
successFullyAddedItems.push(itemToAdd);
|
|
982
|
+
}
|
|
983
|
+
else if (relation === 'after') {
|
|
984
|
+
config.items.splice(topLevelMenuDefinitionIndex + 1, 0, itemToAdd.menu);
|
|
985
|
+
successFullyAddedItems.push(itemToAdd);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
// Adding a sub-menu to an existing items group.
|
|
989
|
+
else {
|
|
990
|
+
const wasAdded = addMenuOrItemToGroup(config, itemToAdd.menu, relativeId, relation);
|
|
991
|
+
if (wasAdded) {
|
|
992
|
+
successFullyAddedItems.push(itemToAdd);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
// Adding a group.
|
|
998
|
+
else if (isMenuBarMenuGroupAddition(itemToAdd)) {
|
|
999
|
+
walkConfigMenus(config.items, menuDefinition => {
|
|
1000
|
+
if (menuDefinition.menuId === relativeId) {
|
|
1001
|
+
// Add a group at the start of a menu.
|
|
1002
|
+
if (relation === 'start') {
|
|
1003
|
+
menuDefinition.groups.unshift(itemToAdd.group);
|
|
1004
|
+
successFullyAddedItems.push(itemToAdd);
|
|
1005
|
+
}
|
|
1006
|
+
// Add a group at the end of a menu.
|
|
1007
|
+
else if (relation === 'end') {
|
|
1008
|
+
menuDefinition.groups.push(itemToAdd.group);
|
|
1009
|
+
successFullyAddedItems.push(itemToAdd);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
const relativeGroupIndex = menuDefinition.groups.findIndex(group => group.groupId === relativeId);
|
|
1014
|
+
if (relativeGroupIndex !== -1) {
|
|
1015
|
+
// Add a group before an existing group in a menu.
|
|
1016
|
+
if (relation === 'before') {
|
|
1017
|
+
menuDefinition.groups.splice(relativeGroupIndex, 0, itemToAdd.group);
|
|
1018
|
+
successFullyAddedItems.push(itemToAdd);
|
|
1019
|
+
}
|
|
1020
|
+
// Add a group after an existing group in a menu.
|
|
1021
|
+
else if (relation === 'after') {
|
|
1022
|
+
menuDefinition.groups.splice(relativeGroupIndex + 1, 0, itemToAdd.group);
|
|
1023
|
+
successFullyAddedItems.push(itemToAdd);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
// Adding an item to an existing items group.
|
|
1030
|
+
else {
|
|
1031
|
+
const wasAdded = addMenuOrItemToGroup(config, itemToAdd.item, relativeId, relation);
|
|
1032
|
+
if (wasAdded) {
|
|
1033
|
+
successFullyAddedItems.push(itemToAdd);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
for (const addedItemConfig of itemsToBeAdded) {
|
|
1038
|
+
if (!successFullyAddedItems.includes(addedItemConfig)) {
|
|
1039
|
+
/**
|
|
1040
|
+
* There was a problem processing the configuration of the menu bar. The configured item could not be added
|
|
1041
|
+
* because the position it was supposed to be added to does not exist.
|
|
1042
|
+
*
|
|
1043
|
+
* This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
|
|
1044
|
+
* to provide a menu bar item has not been loaded or there is a typo in the
|
|
1045
|
+
* {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
|
|
1046
|
+
*
|
|
1047
|
+
* @error menu-bar-item-could-not-be-removed
|
|
1048
|
+
* @param menuBarConfig The full configuration of the menu bar.
|
|
1049
|
+
* @param itemName The name of the item that was not removed from the menu bar.
|
|
1050
|
+
*/
|
|
1051
|
+
logWarning('menu-bar-item-could-not-be-added', {
|
|
1052
|
+
menuBarConfig: originalConfig,
|
|
1053
|
+
addedItemConfig
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Handles adding a sub-menu or an item into a group. The logic is the same for both cases.
|
|
1060
|
+
*/
|
|
1061
|
+
function addMenuOrItemToGroup(config, itemOrMenuToAdd, relativeId, relation) {
|
|
1062
|
+
let wasAdded = false;
|
|
1063
|
+
walkConfigMenus(config.items, menuDefinition => {
|
|
1064
|
+
for (const { groupId, items: groupItems } of menuDefinition.groups) {
|
|
1065
|
+
// Avoid infinite loops.
|
|
1066
|
+
if (wasAdded) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (groupId === relativeId) {
|
|
1070
|
+
// Adding an item/menu at the beginning of a group.
|
|
1071
|
+
if (relation === 'start') {
|
|
1072
|
+
groupItems.unshift(itemOrMenuToAdd);
|
|
1073
|
+
wasAdded = true;
|
|
1074
|
+
}
|
|
1075
|
+
// Adding an item/menu at the end of a group.
|
|
1076
|
+
else if (relation === 'end') {
|
|
1077
|
+
groupItems.push(itemOrMenuToAdd);
|
|
1078
|
+
wasAdded = true;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
// Adding an item/menu relative to an existing item/menu.
|
|
1083
|
+
const relativeItemIndex = groupItems.findIndex(groupItem => {
|
|
1084
|
+
return getIdFromGroupItem(groupItem) === relativeId;
|
|
1085
|
+
});
|
|
1086
|
+
if (relativeItemIndex !== -1) {
|
|
1087
|
+
if (relation === 'before') {
|
|
1088
|
+
groupItems.splice(relativeItemIndex, 0, itemOrMenuToAdd);
|
|
1089
|
+
wasAdded = true;
|
|
1090
|
+
}
|
|
1091
|
+
else if (relation === 'after') {
|
|
1092
|
+
groupItems.splice(relativeItemIndex + 1, 0, itemOrMenuToAdd);
|
|
1093
|
+
wasAdded = true;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
return wasAdded;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Removes components from the menu bar configuration that are not available in the factory and would
|
|
1103
|
+
* not be instantiated. Warns about missing components if the menu bar configuration was specified by the user.
|
|
1104
|
+
*/
|
|
1105
|
+
function purgeUnavailableComponents(originalConfig, config, componentFactory) {
|
|
1106
|
+
walkConfigMenus(config.items, menuDefinition => {
|
|
1107
|
+
for (const groupDefinition of menuDefinition.groups) {
|
|
1108
|
+
groupDefinition.items = groupDefinition.items.filter(item => {
|
|
1109
|
+
const isItemUnavailable = typeof item === 'string' && !componentFactory.has(item);
|
|
1110
|
+
// The default configuration contains all possible editor features. But integrators' editors rarely load
|
|
1111
|
+
// every possible feature. This is why we do not want to log warnings about unavailable items for the default config
|
|
1112
|
+
// because they would show up in almost every integration. If the configuration has been provided by
|
|
1113
|
+
// the integrator, on the other hand, then these warnings bring value.
|
|
1114
|
+
if (isItemUnavailable && !config.isUsingDefaultConfig) {
|
|
1115
|
+
/**
|
|
1116
|
+
* There was a problem processing the configuration of the menu bar. The item with the given
|
|
1117
|
+
* name does not exist so it was omitted when rendering the menu bar.
|
|
1118
|
+
*
|
|
1119
|
+
* This warning usually shows up when the {@link module:core/plugin~Plugin} which is supposed
|
|
1120
|
+
* to provide a menu bar item has not been loaded or there is a typo in the
|
|
1121
|
+
* {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration}.
|
|
1122
|
+
*
|
|
1123
|
+
* Make sure the plugin responsible for this menu bar item is loaded and the menu bar configuration
|
|
1124
|
+
* is correct, e.g. {@link module:basic-styles/bold/boldui~BoldUI} is loaded for the `'menuBar:bold'`
|
|
1125
|
+
* menu bar item.
|
|
1126
|
+
*
|
|
1127
|
+
* @error menu-bar-item-unavailable
|
|
1128
|
+
* @param menuBarConfig The full configuration of the menu bar.
|
|
1129
|
+
* @param parentMenuConfig The config of the menu the unavailable component was defined in.
|
|
1130
|
+
* @param componentName The name of the unavailable component.
|
|
1131
|
+
*/
|
|
1132
|
+
logWarning('menu-bar-item-unavailable', {
|
|
1133
|
+
menuBarConfig: originalConfig,
|
|
1134
|
+
parentMenuConfig: cloneDeep(menuDefinition),
|
|
1135
|
+
componentName: item
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
return !isItemUnavailable;
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Removes empty menus from the menu bar configuration to improve the visual UX. Such menus can occur
|
|
1145
|
+
* when some plugins responsible for providing menu bar items have not been loaded and some part of
|
|
1146
|
+
* the configuration populated menus using these components exclusively.
|
|
1147
|
+
*/
|
|
1148
|
+
function purgeEmptyMenus(originalConfig, config) {
|
|
1149
|
+
const isUsingDefaultConfig = config.isUsingDefaultConfig;
|
|
1150
|
+
let wasSubMenuPurged = false;
|
|
1151
|
+
// Purge top-level menus.
|
|
1152
|
+
config.items = config.items.filter(menuDefinition => {
|
|
1153
|
+
if (!menuDefinition.groups.length) {
|
|
1154
|
+
warnAboutEmptyMenu(originalConfig, menuDefinition, isUsingDefaultConfig);
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
return true;
|
|
1158
|
+
});
|
|
1159
|
+
// Warn if there were no top-level menus left in the menu bar after purging.
|
|
1160
|
+
if (!config.items.length) {
|
|
1161
|
+
warnAboutEmptyMenu(originalConfig, originalConfig, isUsingDefaultConfig);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
// Purge sub-menus and groups.
|
|
1165
|
+
walkConfigMenus(config.items, menuDefinition => {
|
|
1166
|
+
// Get rid of empty groups.
|
|
1167
|
+
menuDefinition.groups = menuDefinition.groups.filter(groupDefinition => {
|
|
1168
|
+
if (!groupDefinition.items.length) {
|
|
1169
|
+
wasSubMenuPurged = true;
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
return true;
|
|
1173
|
+
});
|
|
1174
|
+
// Get rid of empty sub-menus.
|
|
1175
|
+
for (const groupDefinition of menuDefinition.groups) {
|
|
1176
|
+
groupDefinition.items = groupDefinition.items.filter(item => {
|
|
1177
|
+
// If no groups were left after removing empty ones.
|
|
1178
|
+
if (isMenuDefinition(item) && !item.groups.length) {
|
|
1179
|
+
warnAboutEmptyMenu(originalConfig, item, isUsingDefaultConfig);
|
|
1180
|
+
wasSubMenuPurged = true;
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
return true;
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
if (wasSubMenuPurged) {
|
|
1188
|
+
// The config is walked from the root to the leaves so if anything gets removed, we need to re-run the
|
|
1189
|
+
// whole process because it could've affected parents.
|
|
1190
|
+
purgeEmptyMenus(originalConfig, config);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function warnAboutEmptyMenu(originalConfig, emptyMenuConfig, isUsingDefaultConfig) {
|
|
1194
|
+
if (isUsingDefaultConfig) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* There was a problem processing the configuration of the menu bar. One of the menus
|
|
1199
|
+
* is empty so it was omitted when rendering the menu bar.
|
|
1200
|
+
*
|
|
1201
|
+
* This warning usually shows up when some {@link module:core/plugin~Plugin plugins} responsible for
|
|
1202
|
+
* providing menu bar items have not been loaded and the
|
|
1203
|
+
* {@link module:core/editor/editorconfig~EditorConfig#menuBar menu bar configuration} was not updated.
|
|
1204
|
+
*
|
|
1205
|
+
* Make sure all necessary editor plugins are loaded and/or update the menu bar configuration
|
|
1206
|
+
* to account for the missing menu items.
|
|
1207
|
+
*
|
|
1208
|
+
* @error menu-bar-menu-empty
|
|
1209
|
+
* @param menuBarConfig The full configuration of the menu bar.
|
|
1210
|
+
* @param emptyMenuConfig The definition of the menu that has no child items.
|
|
1211
|
+
*/
|
|
1212
|
+
logWarning('menu-bar-menu-empty', {
|
|
1213
|
+
menuBarConfig: originalConfig,
|
|
1214
|
+
emptyMenuConfig
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Localizes the user-config using pre-defined localized category labels.
|
|
1219
|
+
*/
|
|
1220
|
+
function localizeMenuLabels(config, locale) {
|
|
1221
|
+
const t = locale.t;
|
|
1222
|
+
const localizedCategoryLabels = {
|
|
1223
|
+
// Top-level categories.
|
|
1224
|
+
'File': t({
|
|
1225
|
+
string: 'File',
|
|
1226
|
+
id: 'MENU_BAR_MENU_FILE'
|
|
1227
|
+
}),
|
|
1228
|
+
'Edit': t({
|
|
1229
|
+
string: 'Edit',
|
|
1230
|
+
id: 'MENU_BAR_MENU_EDIT'
|
|
1231
|
+
}),
|
|
1232
|
+
'View': t({
|
|
1233
|
+
string: 'View',
|
|
1234
|
+
id: 'MENU_BAR_MENU_VIEW'
|
|
1235
|
+
}),
|
|
1236
|
+
'Insert': t({
|
|
1237
|
+
string: 'Insert',
|
|
1238
|
+
id: 'MENU_BAR_MENU_INSERT'
|
|
1239
|
+
}),
|
|
1240
|
+
'Format': t({
|
|
1241
|
+
string: 'Format',
|
|
1242
|
+
id: 'MENU_BAR_MENU_FORMAT'
|
|
1243
|
+
}),
|
|
1244
|
+
'Tools': t({
|
|
1245
|
+
string: 'Tools',
|
|
1246
|
+
id: 'MENU_BAR_MENU_TOOLS'
|
|
1247
|
+
}),
|
|
1248
|
+
'Help': t({
|
|
1249
|
+
string: 'Help',
|
|
1250
|
+
id: 'MENU_BAR_MENU_HELP'
|
|
1251
|
+
}),
|
|
1252
|
+
// Sub-menus.
|
|
1253
|
+
'Text': t({
|
|
1254
|
+
string: 'Text',
|
|
1255
|
+
id: 'MENU_BAR_MENU_TEXT'
|
|
1256
|
+
}),
|
|
1257
|
+
'Font': t({
|
|
1258
|
+
string: 'Font',
|
|
1259
|
+
id: 'MENU_BAR_MENU_FONT'
|
|
1260
|
+
})
|
|
1261
|
+
};
|
|
1262
|
+
walkConfigMenus(config.items, definition => {
|
|
1263
|
+
if (definition.label in localizedCategoryLabels) {
|
|
1264
|
+
definition.label = localizedCategoryLabels[definition.label];
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Recursively visits all menu definitions in the config and calls the callback for each of them.
|
|
1270
|
+
*/
|
|
1271
|
+
function walkConfigMenus(definition, callback) {
|
|
1272
|
+
if (Array.isArray(definition)) {
|
|
1273
|
+
for (const topLevelMenuDefinition of definition) {
|
|
1274
|
+
walk(topLevelMenuDefinition);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function walk(menuDefinition) {
|
|
1278
|
+
callback(menuDefinition);
|
|
1279
|
+
for (const groupDefinition of menuDefinition.groups) {
|
|
1280
|
+
for (const groupItem of groupDefinition.items) {
|
|
1281
|
+
if (isMenuDefinition(groupItem)) {
|
|
1282
|
+
walk(groupItem);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
function isMenuBarMenuAddition(definition) {
|
|
1289
|
+
return typeof definition === 'object' && 'menu' in definition;
|
|
1290
|
+
}
|
|
1291
|
+
function isMenuBarMenuGroupAddition(definition) {
|
|
1292
|
+
return typeof definition === 'object' && 'group' in definition;
|
|
1293
|
+
}
|
|
1294
|
+
function getRelationFromPosition(position) {
|
|
1295
|
+
if (position.startsWith('start')) {
|
|
1296
|
+
return 'start';
|
|
1297
|
+
}
|
|
1298
|
+
else if (position.startsWith('end')) {
|
|
1299
|
+
return 'end';
|
|
1300
|
+
}
|
|
1301
|
+
else if (position.startsWith('after')) {
|
|
1302
|
+
return 'after';
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
return 'before';
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function getRelativeIdFromPosition(position) {
|
|
1309
|
+
const match = position.match(/^[^:]+:(.+)/);
|
|
1310
|
+
if (match) {
|
|
1311
|
+
return match[1];
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
function getIdFromGroupItem(item) {
|
|
1316
|
+
return typeof item === 'string' ? item : item.menuId;
|
|
1317
|
+
}
|
|
1318
|
+
function isMenuDefinition(definition) {
|
|
1319
|
+
return typeof definition === 'object' && 'menuId' in definition;
|
|
1320
|
+
}
|