From 627f842b7997fde21973afa5b196293b685c9b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Remiszewski?= <118178939+mremiszewski@users.noreply.github.com> Date: Tue, 7 May 2024 18:48:15 +0200 Subject: [PATCH] Add menu bar integration to multi root editor. (#16280) Feature (editor-multi-root): Added menu bar support for multi-root editor. --- .../tests/_utils/classictesteditor.js | 2 +- .../src/classiceditorui.ts | 60 ++----- .../tests/classiceditorui.js | 113 ------------- .../src/decouplededitorui.ts | 36 +--- .../tests/decouplededitorui.js | 113 ------------- .../src/multirooteditorui.ts | 2 + .../src/multirooteditoruiview.ts | 20 ++- .../tests/manual/multirooteditor.html | 12 +- .../tests/manual/multirooteditor.js | 1 + .../tests/multirooteditoruiview.js | 40 ++++- packages/ckeditor5-ui/src/index.ts | 2 +- packages/ckeditor5-ui/src/menubar/utils.ts | 40 ++++- .../ckeditor5-ui/tests/editorui/editorui.js | 4 +- packages/ckeditor5-ui/tests/menubar/utils.js | 154 +++++++++++++++++- 14 files changed, 275 insertions(+), 324 deletions(-) diff --git a/packages/ckeditor5-core/tests/_utils/classictesteditor.js b/packages/ckeditor5-core/tests/_utils/classictesteditor.js index 2adbdc67eb4..42f8b7bcf85 100644 --- a/packages/ckeditor5-core/tests/_utils/classictesteditor.js +++ b/packages/ckeditor5-core/tests/_utils/classictesteditor.js @@ -105,7 +105,7 @@ export default class ClassicTestEditor extends ElementApiMixin( Editor ) { * @memberOf tests.core._utils * @extends core.editor.EditorUI */ -class ClassicTestEditorUI extends EditorUI { +export class ClassicTestEditorUI extends EditorUI { /** * @inheritDoc */ diff --git a/packages/ckeditor5-editor-classic/src/classiceditorui.ts b/packages/ckeditor5-editor-classic/src/classiceditorui.ts index bbc000dda16..f243b44c3ee 100644 --- a/packages/ckeditor5-editor-classic/src/classiceditorui.ts +++ b/packages/ckeditor5-editor-classic/src/classiceditorui.ts @@ -10,12 +10,14 @@ import type { Editor, ElementApi } from 'ckeditor5/src/core.js'; import { EditorUI, - normalizeToolbarConfig, - normalizeMenuBarConfig, DialogView, + normalizeToolbarConfig, + _initMenuBar, type DialogViewMoveToEvent, type Dialog, - type EditorUIReadyEvent + type EditorUIReadyEvent, + type EditorUIView, + type MenuBarView } from 'ckeditor5/src/ui.js'; import { enablePlaceholder, @@ -38,11 +40,6 @@ export default class ClassicEditorUI extends EditorUI { */ private readonly _toolbarConfig: ReturnType; - /** - * A normalized `config.menuBar` object. - */ - private readonly _menuBarConfig: ReturnType; - /** * The element replacer instance used to hide the editor's source element. */ @@ -60,9 +57,6 @@ export default class ClassicEditorUI extends EditorUI { this.view = view; this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) ); - // We use config.define in ClassicEditor, there will always be some configuration. - this._menuBarConfig = normalizeMenuBarConfig( editor.config.get( 'menuBar' ) || {} ); - this._elementReplacer = new ElementReplacer(); this.listenTo( @@ -124,7 +118,11 @@ export default class ClassicEditorUI extends EditorUI { this._initPlaceholder(); this._initToolbar(); - this._initMenuBar(); + + if ( view.menuBarView ) { + _initMenuBar( editor, view.menuBarView ); + } + this._initDialogPluginIntegration(); this.fire( 'ready' ); } @@ -160,21 +158,6 @@ export default class ClassicEditorUI extends EditorUI { this.addToolbar( view.toolbar ); } - /** - * Initializes the editor menu bar. - */ - private _initMenuBar(): void { - const view = this.view; - - if ( !view.menuBarView ) { - return; - } - - this._setupMenuBarBehaviors( view.menuBarView.element! ); - - view.menuBarView.fillFromConfig( this._menuBarConfig, this.componentFactory ); - } - /** * Enable the placeholder text on the editing root. */ @@ -275,28 +258,5 @@ export default class ClassicEditorUI extends EditorUI { }, { priority: 'high' } ); }, { priority: 'low' } ); } - - /** - * Handles focus and keystrokes for menu bar element. - */ - private _setupMenuBarBehaviors( menuBarViewElement: HTMLElement ) { - const editor = this.editor; - this.focusTracker.add( menuBarViewElement ); - editor.keystrokes.listenTo( menuBarViewElement ); - - editor.keystrokes.set( 'Esc', ( data, cancel ) => { - if ( menuBarViewElement.contains( this.focusTracker.focusedElement ) ) { - editor.editing.view.focus(); - cancel(); - } - } ); - - editor.keystrokes.set( 'Alt+F9', ( data, cancel ) => { - if ( !menuBarViewElement.contains( this.focusTracker.focusedElement ) ) { - this.view.menuBarView!.focus(); - cancel(); - } - } ); - } } diff --git a/packages/ckeditor5-editor-classic/tests/classiceditorui.js b/packages/ckeditor5-editor-classic/tests/classiceditorui.js index dac99eda49f..81e5ec05c62 100644 --- a/packages/ckeditor5-editor-classic/tests/classiceditorui.js +++ b/packages/ckeditor5-editor-classic/tests/classiceditorui.js @@ -765,119 +765,6 @@ describe( 'Focus handling and navigation between editing root and editor toolbar } ); } ); -describe( 'Focus handling and navigation between editing root and editor menu bar', () => { - let editorElement, editor, ui, menuBarView, domRoot; - - testUtils.createSinonSandbox(); - - beforeEach( async () => { - editorElement = document.body.appendChild( document.createElement( 'div' ) ); - - editor = await ClassicEditor.create( editorElement, { - plugins: [ Paragraph, Image, ImageToolbar, ImageCaption ], - toolbar: [ 'imageTextAlternative' ], - image: { - toolbar: [ 'toggleImageCaption' ] - }, - menuBar: { - isVisible: true - } - } ); - - domRoot = editor.editing.view.domRoots.get( 'main' ); - - ui = editor.ui; - menuBarView = ui.view.menuBarView; - } ); - - afterEach( () => { - editorElement.remove(); - - return editor.destroy(); - } ); - - describe( 'Focusing menu bar on Alt+F9 key press', () => { - beforeEach( () => { - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - } ); - - it( 'should focus the menu bar when the focus is in the editing root', () => { - const spy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - - // Focus the menu bar. - pressAltF9( editor ); - - sinon.assert.calledOnce( spy ); - } ); - - it( 'should do nothing if the menu bar is already focused', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - // Focus the menu bar. - pressAltF9( editor ); - ui.focusTracker.focusedElement = menuBarView.element; - - // Try Alt+F9 again. - pressAltF9( editor ); - - sinon.assert.calledOnce( menuBarFocusSpy ); - sinon.assert.notCalled( domRootFocusSpy ); - } ); - } ); - - describe( 'Restoring focus on Esc key press', () => { - beforeEach( () => { - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - } ); - - it( 'should move the focus back from the menu bar to the editing root', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - // Focus the menu bar. - pressAltF9( editor ); - ui.focusTracker.focusedElement = menuBarView.element; - - pressEsc( editor ); - - sinon.assert.callOrder( menuBarFocusSpy, domRootFocusSpy ); - } ); - - it( 'should do nothing if it was pressed when menu bar was not focused', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - pressEsc( editor ); - - sinon.assert.notCalled( domRootFocusSpy ); - sinon.assert.notCalled( menuBarFocusSpy ); - } ); - } ); -} ); - -function pressAltF9( editor ) { - editor.keystrokes.press( { - keyCode: keyCodes.f9, - altKey: true, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - } ); -} - function pressAltF10( editor ) { editor.keystrokes.press( { keyCode: keyCodes.f10, diff --git a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.ts b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.ts index 6c7dff52cbc..5f635aa6162 100644 --- a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.ts +++ b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.ts @@ -13,8 +13,8 @@ import { import { EditorUI, - normalizeMenuBarConfig, - type EditorUIReadyEvent + type EditorUIReadyEvent, + _initMenuBar } from 'ckeditor5/src/ui.js'; import { enablePlaceholder } from 'ckeditor5/src/engine.js'; @@ -81,7 +81,7 @@ export default class DecoupledEditorUI extends EditorUI { this._initPlaceholder(); this._initToolbar(); - this._initMenuBar(); + _initMenuBar( editor, this.view.menuBarView ); this.fire( 'ready' ); } @@ -112,36 +112,6 @@ export default class DecoupledEditorUI extends EditorUI { this.addToolbar( view.toolbar ); } - /** - * Initializes the editor menu bar. - */ - private _initMenuBar(): void { - const editor = this.editor; - const menuBarViewElement = this.view.menuBarView.element!; - const view = this.view; - - this.focusTracker.add( menuBarViewElement ); - editor.keystrokes.listenTo( menuBarViewElement ); - - const normalizedMenuBarConfig = normalizeMenuBarConfig( editor.config.get( 'menuBar' ) || {} ); - - view.menuBarView.fillFromConfig( normalizedMenuBarConfig, this.componentFactory ); - - editor.keystrokes.set( 'Esc', ( data, cancel ) => { - if ( menuBarViewElement.contains( this.focusTracker.focusedElement ) ) { - editor.editing.view.focus(); - cancel(); - } - } ); - - editor.keystrokes.set( 'Alt+F9', ( data, cancel ) => { - if ( !menuBarViewElement.contains( this.focusTracker.focusedElement ) ) { - this.view.menuBarView.focus(); - cancel(); - } - } ); - } - /** * Enable the placeholder text on the editing root. */ diff --git a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js index 299d93ba99c..dd19a6e791a 100644 --- a/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js +++ b/packages/ckeditor5-editor-decoupled/tests/decouplededitorui.js @@ -11,7 +11,6 @@ import DecoupledEditor from '../src/decouplededitor.js'; import DecoupledEditorUI from '../src/decouplededitorui.js'; import DecoupledEditorUIView from '../src/decouplededitoruiview.js'; import EditorUI from '@ckeditor/ckeditor5-ui/src/editorui/editorui.js'; -import Heading from '@ckeditor/ckeditor5-heading/src/heading.js'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph.js'; import { Image, ImageCaption, ImageToolbar } from '@ckeditor/ckeditor5-image'; @@ -391,109 +390,6 @@ describe( 'Focus handling and navigation between editing root and editor toolbar } ); } ); -describe( 'Focus handling and navigation between editing root and menu bar', () => { - let editorElement, editor, ui, menuBarView, domRoot; - - testUtils.createSinonSandbox(); - - beforeEach( async () => { - editorElement = document.body.appendChild( document.createElement( 'div' ) ); - - editor = await DecoupledEditor.create( editorElement, { - plugins: [ Paragraph, Heading, Image, ImageToolbar, ImageCaption ], - toolbar: [ 'imageTextAlternative' ], - image: { - toolbar: [ 'toggleImageCaption' ] - } - } ); - - domRoot = editor.editing.view.domRoots.get( 'main' ); - - ui = editor.ui; - menuBarView = ui.view.menuBarView; - - document.body.appendChild( menuBarView.element ); - } ); - - afterEach( () => { - editorElement.remove(); - menuBarView.element.remove(); - - return editor.destroy(); - } ); - - describe( 'Focusing menu bar on Alt+F9 key press', () => { - beforeEach( () => { - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - } ); - - it( 'should focus the menu bar when the focus is in the editing root', () => { - const spy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - - pressAltF9( editor ); - - sinon.assert.calledOnce( spy ); - } ); - - it( 'should do nothing if the menu bar is already focused', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - // Focus the toolbar. - pressAltF9( editor ); - ui.focusTracker.focusedElement = menuBarView.element; - - // Try Alt+F9 again. - pressAltF9( editor ); - - sinon.assert.calledOnce( menuBarFocusSpy ); - sinon.assert.notCalled( domRootFocusSpy ); - } ); - } ); - - describe( 'Restoring focus on Esc key press', () => { - beforeEach( () => { - ui.focusTracker.isFocused = true; - ui.focusTracker.focusedElement = domRoot; - } ); - - it( 'should move the focus back from the main toolbar to the editing root', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - // Focus the menu bar. - pressAltF9( editor ); - ui.focusTracker.focusedElement = menuBarView.element; - - pressEsc( editor ); - - sinon.assert.callOrder( menuBarFocusSpy, domRootFocusSpy ); - } ); - - it( 'should do nothing if it was pressed when menu bar was not focused', () => { - const domRootFocusSpy = testUtils.sinon.spy( domRoot, 'focus' ); - const menuBarFocusSpy = testUtils.sinon.spy( menuBarView, 'focus' ); - - setModelData( editor.model, 'foo[]' ); - - pressEsc( editor ); - - sinon.assert.notCalled( domRootFocusSpy ); - sinon.assert.notCalled( menuBarFocusSpy ); - } ); - } ); -} ); - function viewCreator( name ) { return locale => { const view = new View( locale ); @@ -505,15 +401,6 @@ function viewCreator( name ) { }; } -function pressAltF9( editor ) { - editor.keystrokes.press( { - keyCode: keyCodes.f9, - altKey: true, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - } ); -} - function pressAltF10( editor ) { editor.keystrokes.press( { keyCode: keyCodes.f10, diff --git a/packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts b/packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts index ad8fc20591b..5f8e87caf60 100644 --- a/packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts +++ b/packages/ckeditor5-editor-multi-root/src/multirooteditorui.ts @@ -13,6 +13,7 @@ import { import { EditorUI, + _initMenuBar, type EditorUIReadyEvent, type InlineEditableUIView } from 'ckeditor5/src/ui.js'; @@ -84,6 +85,7 @@ export default class MultiRootEditorUI extends EditorUI { } this._initToolbar(); + _initMenuBar( this.editor, this.view.menuBarView ); this.fire( 'ready' ); } diff --git a/packages/ckeditor5-editor-multi-root/src/multirooteditoruiview.ts b/packages/ckeditor5-editor-multi-root/src/multirooteditoruiview.ts index 654c133e601..aca42e2b05a 100644 --- a/packages/ckeditor5-editor-multi-root/src/multirooteditoruiview.ts +++ b/packages/ckeditor5-editor-multi-root/src/multirooteditoruiview.ts @@ -7,7 +7,7 @@ * @module editor-multi-root/multirooteditoruiview */ -import { EditorUIView, InlineEditableUIView, ToolbarView } from 'ckeditor5/src/ui.js'; +import { EditorUIView, InlineEditableUIView, MenuBarView, ToolbarView } from 'ckeditor5/src/ui.js'; import type { Locale } from 'ckeditor5/src/utils.js'; import type { EditingView } from 'ckeditor5/src/engine.js'; @@ -26,6 +26,11 @@ export default class MultiRootEditorUIView extends EditorUIView { */ public readonly toolbar: ToolbarView; + /** + * Menu bar view instance. + */ + public readonly menuBarView: MenuBarView; + /** * Editable elements used by the multi-root editor UI. */ @@ -70,6 +75,8 @@ export default class MultiRootEditorUIView extends EditorUIView { shouldGroupWhenFull: options.shouldToolbarGroupWhenFull } ); + this.menuBarView = new MenuBarView( locale ); + this.editables = {}; // Create `InlineEditableUIView` instance for each editable. @@ -94,6 +101,16 @@ export default class MultiRootEditorUIView extends EditorUIView { dir: locale.uiLanguageDirection } } ); + + this.menuBarView.extendTemplate( { + attributes: { + class: [ + 'ck-reset_all', + 'ck-rounded-corners' + ], + dir: locale.uiLanguageDirection + } + } ); } /** @@ -150,5 +167,6 @@ export default class MultiRootEditorUIView extends EditorUIView { this.registerChild( Object.values( this.editables ) ); this.registerChild( this.toolbar ); + this.registerChild( this.menuBarView ); } } diff --git a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html index a3fda27a54b..5c76b005872 100644 --- a/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html +++ b/packages/ckeditor5-editor-multi-root/tests/manual/multirooteditor.html @@ -3,6 +3,9 @@

+

The menubar

+ +

The toolbar

@@ -15,13 +18,15 @@

The editable