Skip to content

Commit

Permalink
Merge pull request #16474 from ckeditor/ck/app-menu-bar-special-chara…
Browse files Browse the repository at this point in the history
…cters

Feature (special-characters): Added menu bar integration for special characters. New component `menuBar:specialCharacters` is now by default added in "Insert" menu. Closes #16501.

Other (special-characters):  Special characters plugin now uses a dialog (instead of a toolbar dropdown).

MINOR BREAKING CHANGE (special-characters):   Special characters plugin now uses a dialog (instead of a toolbar dropdown).
  • Loading branch information
scofalik authored Jun 10, 2024
2 parents 9650768 + aba4123 commit 1145286
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 405 deletions.
4 changes: 2 additions & 2 deletions packages/ckeditor5-special-characters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"type": "module",
"main": "src/index.ts",
"dependencies": {
"ckeditor5": "41.4.2"
"ckeditor5": "41.4.2",
"@ckeditor/ckeditor5-ui": "41.4.2"
},
"devDependencies": {
"@ckeditor/ckeditor5-cloud-services": "41.4.2",
Expand All @@ -24,7 +25,6 @@
"@ckeditor/ckeditor5-image": "41.4.2",
"@ckeditor/ckeditor5-theme-lark": "41.4.2",
"@ckeditor/ckeditor5-typing": "41.4.2",
"@ckeditor/ckeditor5-ui": "41.4.2",
"@ckeditor/ckeditor5-utils": "41.4.2",
"typescript": "5.0.4",
"webpack": "^5.58.1",
Expand Down
130 changes: 78 additions & 52 deletions packages/ckeditor5-special-characters/src/specialcharacters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
*/

import { Plugin, type Editor } from 'ckeditor5/src/core.js';
import { Typing, type InsertTextCommand } from 'ckeditor5/src/typing.js';
import { createDropdown, type DropdownView } from 'ckeditor5/src/ui.js';
import { Typing } from 'ckeditor5/src/typing.js';
import { ButtonView, MenuBarMenuListItemButtonView, DialogViewPosition, Dialog } from 'ckeditor5/src/ui.js';
import { CKEditorError, type Locale } from 'ckeditor5/src/utils.js';
import SpecialCharactersNavigationView from './ui/specialcharactersnavigationview.js';
import CharacterGridView, {
type CharacterGridViewExecuteEvent,
type CharacterGridViewTileFocusEvent,
Expand All @@ -23,6 +22,7 @@ import SpecialCharactersView from './ui/specialcharactersview.js';
import specialCharactersIcon from '../theme/icons/specialcharacters.svg';

import '../theme/specialcharacters.css';
import SpecialCharactersCategoriesView from './ui/specialcharacterscategoriesview.js';

const ALL_SPECIAL_CHARACTERS_GROUP = 'All';

Expand Down Expand Up @@ -51,7 +51,7 @@ export default class SpecialCharacters extends Plugin {
* @inheritDoc
*/
public static get requires() {
return [ Typing ] as const;
return [ Typing, Dialog ] as const;
}

/**
Expand Down Expand Up @@ -79,50 +79,19 @@ export default class SpecialCharacters extends Plugin {
*/
public init(): void {
const editor = this.editor;
const t = editor.t;

const inputCommand: InsertTextCommand = editor.commands.get( 'insertText' )!;

// Add the `specialCharacters` dropdown button to feature components.
editor.ui.componentFactory.add( 'specialCharacters', locale => {
const dropdownView = createDropdown( locale );
let dropdownPanelContent: DropdownPanelContent;
editor.ui.componentFactory.add( 'specialCharacters', () => {
const button = this._createDialogButton( ButtonView );

dropdownView.buttonView.set( {
label: t( 'Special characters' ),
icon: specialCharactersIcon,
button.set( {
tooltip: true
} );

dropdownView.bind( 'isEnabled' ).to( inputCommand );

// Insert a special character when a tile was clicked.
dropdownView.on<CharacterGridViewExecuteEvent>( 'execute', ( evt, data ) => {
editor.execute( 'insertText', { text: data.character } );
editor.editing.view.focus();
} );

dropdownView.on( 'change:isOpen', () => {
if ( !dropdownPanelContent ) {
dropdownPanelContent = this._createDropdownPanelContent( locale, dropdownView );

const specialCharactersView = new SpecialCharactersView(
locale,
dropdownPanelContent.navigationView,
dropdownPanelContent.gridView,
dropdownPanelContent.infoView
);

dropdownView.panelView.children.add( specialCharactersView );
}

dropdownPanelContent.infoView.set( {
character: null,
name: null
} );
} );
return button;
} );

return dropdownView;
editor.ui.componentFactory.add( 'menuBar:specialCharacters', () => {
return this._createDialogButton( MenuBarMenuListItemButtonView );
} );
}

Expand Down Expand Up @@ -240,9 +209,9 @@ export default class SpecialCharacters extends Plugin {
/**
* Initializes the dropdown, used for lazy loading.
*
* @returns An object with `navigationView`, `gridView` and `infoView` properties, containing UI parts.
* @returns An object with `categoriesView`, `gridView` and `infoView` properties, containing UI parts.
*/
private _createDropdownPanelContent( locale: Locale, dropdownView: DropdownView ): DropdownPanelContent {
private _createDropdownPanelContent( locale: Locale ): DropdownPanelContent {
const groupEntries: Array<[ string, string ]> = Array
.from( this.getGroups() )
.map( name => ( [ name, this._groups.get( name )!.label ] ) );
Expand All @@ -254,12 +223,10 @@ export default class SpecialCharacters extends Plugin {
...groupEntries
] );

const navigationView = new SpecialCharactersNavigationView( locale, specialCharsGroups );
const categoriesView = new SpecialCharactersCategoriesView( locale, specialCharsGroups );
const gridView = new CharacterGridView( locale );
const infoView = new CharacterInfoView( locale );

gridView.delegate( 'execute' ).to( dropdownView );

gridView.on<CharacterGridViewTileHoverEvent>( 'tileHover', ( evt, data ) => {
infoView.set( data );
} );
Expand All @@ -269,14 +236,73 @@ export default class SpecialCharacters extends Plugin {
} );

// Update the grid of special characters when a user changed the character group.
navigationView.on( 'execute', () => {
this._updateGrid( navigationView.currentGroupName, gridView );
categoriesView.on( 'change:currentGroupName', ( evt, propertyName, newValue ) => {
this._updateGrid( newValue, gridView );
} );

// Set the initial content of the special characters grid.
this._updateGrid( navigationView.currentGroupName, gridView );
this._updateGrid( categoriesView.currentGroupName, gridView );

return { navigationView, gridView, infoView };
return { categoriesView, gridView, infoView };
}

/**
* Creates a button for for menu bar that will show special characetrs dialog.
*/
private _createDialogButton<T extends typeof ButtonView | typeof MenuBarMenuListItemButtonView>( ButtonClass: T ): InstanceType<T> {
const editor = this.editor;
const locale = editor.locale;
const buttonView = new ButtonClass( editor.locale ) as InstanceType<T>;
const command = editor.commands.get( 'insertText' )!;
const t = locale.t;
const dialogPlugin = this.editor.plugins.get( 'Dialog' );

buttonView.set( {
label: t( 'Special characters' ),
icon: specialCharactersIcon
} );

buttonView.bind( 'isOn' ).to( dialogPlugin, 'id', id => id === 'specialCharacters' );
buttonView.bind( 'isEnabled' ).to( command, 'isEnabled' );

buttonView.on( 'execute', () => {
if ( dialogPlugin.id === 'specialCharacters' ) {
dialogPlugin.hide();

return;
}

this._showDialog();
} );

return buttonView;
}

private _showDialog() {
const editor = this.editor;
const dialog = editor.plugins.get( 'Dialog' );
const locale = editor.locale;
const t = locale.t;

const { categoriesView, gridView, infoView } = this._createDropdownPanelContent( locale );
const content = new SpecialCharactersView(
locale,
categoriesView,
gridView,
infoView
);

gridView.on<CharacterGridViewExecuteEvent>( 'execute', ( evt, data ) => {
editor.execute( 'insertText', { text: data.character } );
} );

dialog.show( {
id: 'specialCharacters',
title: t( 'Special characters' ),
className: 'ck-special-characters',
content,
position: DialogViewPosition.EDITOR_TOP_SIDE
} );
}
}

Expand All @@ -299,7 +325,7 @@ interface Group {
}

interface DropdownPanelContent {
navigationView: SpecialCharactersNavigationView;
categoriesView: SpecialCharactersCategoriesView;
gridView: CharacterGridView;
infoView: CharacterInfoView;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module special-characters/ui/specialcharacterscategoriesview
*/

import { type Locale, Collection } from 'ckeditor5/src/utils.js';
import {
addListToDropdown,
createLabeledDropdown,
LabeledFieldView,
View,
ViewModel,
type DropdownView,
type ListDropdownItemDefinition
} from 'ckeditor5/src/ui.js';

/**
* A class representing the navigation part of the special characters UI. It is responsible
* for describing the feature and allowing the user to select a particular character group.
*/
export default class SpecialCharactersCategoriesView extends View {
/**
* Currently selected special characters group's name.
*/
declare public currentGroupName: string;

private _groupNames: Map<string, string>;

private _dropdownView: LabeledFieldView<DropdownView>;

/**
* Creates an instance of the {@link module:special-characters/ui/specialcharacterscategoriesview~SpecialCharactersCategoriesView}
* class.
*
* @param locale The localization services instance.
* @param groupNames The names of the character groups.
*/
constructor( locale: Locale, groupNames: Map<string, string> ) {
super( locale );

this.set( 'currentGroupName', Array.from( groupNames.entries() )[ 0 ][ 0 ] );
this._groupNames = groupNames;
this._dropdownView = new LabeledFieldView( locale, createLabeledDropdown );

this.setTemplate( {
tag: 'div',
attributes: {
class: [ 'ck', 'ck-character-categories' ]
},
children: [
this._dropdownView
]
} );
}

/**
* @inheritDoc
*/
public override render(): void {
super.render();

this._setupDropdown();
}

/**
* @inheritDoc
*/
public focus(): void {
this._dropdownView.focus();
}

/**
* Creates dropdown item list, sets up bindings and fills properties.
*/
private _setupDropdown(): void {
const items = new Collection<ListDropdownItemDefinition>();

for ( const [ name, label ] of this._groupNames ) {
const item: ListDropdownItemDefinition = {
type: 'button',
model: new ViewModel( {
name,
label,
withText: true
} )
};

item.model.bind( 'isOn' ).to( this, 'currentGroupName', value => {
return value === name;
} );

items.add( item );
}

const t = this.locale!.t;
const accessibleLabel = t( 'Category' );

this._dropdownView.set( {
label: accessibleLabel,
isEmpty: false
} );

this._dropdownView.fieldView.panelPosition = this.locale!.uiLanguageDirection === 'rtl' ? 'se' : 'sw';

this._dropdownView.fieldView.buttonView.set( {
withText: true,
tooltip: accessibleLabel,
ariaLabel: accessibleLabel,
ariaLabelledBy: undefined,
isOn: false
} );
this._dropdownView.fieldView.buttonView.bind( 'label' )
.to( this, 'currentGroupName', value => this._groupNames.get( value )! );
this._dropdownView.fieldView.on( 'execute', ( { source } ) => {
this.currentGroupName = ( source as ViewModel ).name as string;
} );

addListToDropdown( this._dropdownView.fieldView, items, {
ariaLabel: accessibleLabel,
role: 'menu'
} );
}
}
Loading

0 comments on commit 1145286

Please sign in to comment.