Skip to content

Commit

Permalink
Used list style grid for list properties in the menu bar. Secured the…
Browse files Browse the repository at this point in the history
… menu bar menu against list styles being disallowed in the list properties configuration.
  • Loading branch information
oleq committed Mar 1, 2024
1 parent 7af02fe commit 04674e8
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 99 deletions.
2 changes: 0 additions & 2 deletions packages/ckeditor5-list/src/list/listui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
*/

import { createUIComponents } from './utils.js';

import { icons, Plugin } from 'ckeditor5/src/core.js';
import { MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';

/**
* The list UI feature. It introduces the `'numberedList'` and `'bulletedList'` buttons that
Expand Down
204 changes: 109 additions & 95 deletions packages/ckeditor5-list/src/listproperties/listpropertiesui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ import {
SplitButtonView,
createDropdown,
focusChildOnDropdownOpen,
type DropdownView,
MenuBarMenuView,
MenuBarMenuListView,
MenuBarMenuListItemView,
MenuBarMenuListItemButtonView
type DropdownView
} from 'ckeditor5/src/ui.js';

import type { Locale } from 'ckeditor5/src/utils.js';
Expand All @@ -43,6 +40,7 @@ import listStyleLowerLatinIcon from '../../theme/icons/liststylelowerlatin.svg';
import listStyleUpperLatinIcon from '../../theme/icons/liststyleupperlatin.svg';

import '../../theme/liststyles.css';
import type { ListPropertiesConfig } from '../listconfig.js';

/**
* The list properties UI plugin. It introduces the extended `'bulletedList'` and `'numberedList'` toolbar
Expand All @@ -62,12 +60,12 @@ export default class ListPropertiesUI extends Plugin {
public init(): void {
const editor = this.editor;
const t = editor.locale.t;
const enabledProperties = editor.config.get( 'list.properties' )!;
const propertiesConfig = editor.config.get( 'list.properties' )!;

// Note: When this plugin does not register the "bulletedList" dropdown due to properties configuration,
// a simple button will be still registered under the same name by ListUI as a fallback. This should happen
// in most editor configuration because the List plugin automatically requires ListUI.
if ( enabledProperties.styles ) {
if ( propertiesConfig.styles ) {
const styleDefinitions = [
{
label: t( 'Toggle the disc list style' ),
Expand All @@ -88,26 +86,35 @@ export default class ListPropertiesUI extends Plugin {
icon: listStyleSquareIcon
}
];
const buttonLabel = t( 'Bulleted list' );
const buttonLabel = t( 'Bulleted List' );
const styleGridAriaLabel = t( 'Bulleted list styles toolbar' );
const commandName = 'bulletedList';

editor.ui.componentFactory.add( commandName, getDropdownViewCreator( {
editor,
propertiesConfig,
parentCommandName: commandName,
buttonLabel,
buttonIcon: icons.bulletedList,
styleGridAriaLabel: t( 'Bulleted list styles toolbar' ),
styleGridAriaLabel,
styleDefinitions
} ) );

// Add menu item for bulleted list.
createMenuItem( editor, commandName, buttonLabel, t( 'Bulleted' ), styleDefinitions );
// Add the menu bar item for bulleted list.
editor.ui.componentFactory.add( `menuBar:${ commandName }`, getMenuBarStylesMenuCreator( {
editor,
propertiesConfig,
parentCommandName: commandName,
buttonLabel,
styleGridAriaLabel,
styleDefinitions
} ) );
}

// Note: When this plugin does not register the "numberedList" dropdown due to properties configuration,
// a simple button will be still registered under the same name by ListUI as a fallback. This should happen
// in most editor configuration because the List plugin automatically requires ListUI.
if ( enabledProperties.styles || enabledProperties.startIndex || enabledProperties.reversed ) {
if ( propertiesConfig.styles || propertiesConfig.startIndex || propertiesConfig.reversed ) {
const styleDefinitions = [
{
label: t( 'Toggle the decimal list style' ),
Expand Down Expand Up @@ -146,20 +153,32 @@ export default class ListPropertiesUI extends Plugin {
icon: listStyleUpperLatinIcon
}
];
const buttonLabel = t( 'Numbered list' );
const buttonLabel = t( 'Numbered List' );
const styleGridAriaLabel = t( 'Numbered list styles toolbar' );
const commandName = 'numberedList';

editor.ui.componentFactory.add( commandName, getDropdownViewCreator( {
editor,
propertiesConfig,
parentCommandName: commandName,
buttonLabel,
buttonIcon: icons.numberedList,
styleGridAriaLabel: t( 'Numbered list styles toolbar' ),
styleGridAriaLabel,
styleDefinitions
} ) );

// Add menu item for numbered list.
createMenuItem( editor, commandName, buttonLabel, t( 'Numbered' ), styleDefinitions );
// Menu bar menu does not display list start index or reverse UI. If there are no styles enabled,
// the menu makes no sense and should be omitted.
if ( propertiesConfig.styles ) {
editor.ui.componentFactory.add( `menuBar:${ commandName }`, getMenuBarStylesMenuCreator( {
editor,
propertiesConfig,
parentCommandName: commandName,
buttonLabel,
styleGridAriaLabel,
styleDefinitions
} ) );
}
}
}
}
Expand All @@ -169,6 +188,7 @@ export default class ListPropertiesUI extends Plugin {
* which in turn contains buttons allowing users to change list styles in the context of the current selection.
*
* @param options.editor
* @param options.propertiesConfig List properties configuration.
* @param options.parentCommandName The name of the higher-order editor command associated with
* the set of particular list styles (e.g. "bulletedList" for "disc", "circle", and "square" styles).
* @param options.buttonLabel Label of the main part of the split button.
Expand All @@ -179,13 +199,15 @@ export default class ListPropertiesUI extends Plugin {
*/
function getDropdownViewCreator( {
editor,
propertiesConfig,
parentCommandName,
buttonLabel,
buttonIcon,
styleGridAriaLabel,
styleDefinitions
}: {
editor: Editor;
propertiesConfig: Readonly<ListPropertiesConfig>;
parentCommandName: string;
buttonLabel: string;
buttonIcon: string;
Expand Down Expand Up @@ -219,6 +241,7 @@ function getDropdownViewCreator( {
dropdownView.once( 'change:isOpen', () => {
const listPropertiesView = createListPropertiesView( {
editor,
propertiesConfig,
dropdownView,
parentCommandName,
styleGridAriaLabel,
Expand Down Expand Up @@ -272,9 +295,13 @@ function getStyleButtonCreator( {
button.on( 'execute', () => {
// If the content the selection is anchored to is a list, let's change its style.
if ( parentCommand.value ) {
// Remove the list when the current list style is the same as the one that would normally be applied.
if ( listStyleCommand.value === type ) {
editor.execute( parentCommandName );
}
// If the current list style is not set in the model or the style is different than the
// one to be applied, simply apply the new style.
if ( listStyleCommand.value !== type ) {
else if ( listStyleCommand.value !== type ) {
editor.execute( 'listStyle', { type } );
}
// If the style was the same, remove it (the button works as an off toggle).
Expand All @@ -298,6 +325,7 @@ function getStyleButtonCreator( {
* A helper that creates the properties view for the individual style dropdown.
*
* @param options.editor Editor instance.
* @param options.propertiesConfig List properties configuration.
* @param options.dropdownView Styles dropdown view that hosts the properties view.
* @param options.parentCommandName The name of the higher-order editor command associated with
* the set of particular list styles (e.g. "bulletedList" for "disc", "circle", and "square" styles).
Expand All @@ -306,26 +334,31 @@ function getStyleButtonCreator( {
*/
function createListPropertiesView( {
editor,
propertiesConfig,
dropdownView,
parentCommandName,
styleDefinitions,
styleGridAriaLabel
}: {
editor: Editor;
propertiesConfig: Readonly<ListPropertiesConfig>;
dropdownView: DropdownView;
parentCommandName: string;
styleDefinitions: Array<StyleDefinition>;
styleGridAriaLabel: string;
} ) {
const locale = editor.locale;
const enabledProperties = editor.config.get( 'list.properties' )!;
let styleButtonViews = null;
const enabledProperties = {
...propertiesConfig
};

if ( parentCommandName != 'numberedList' ) {
enabledProperties.startIndex = false;
enabledProperties.reversed = false;
}

let styleButtonViews = null;

if ( enabledProperties.styles ) {
const listStyleCommand: LegacyListStyleCommand | ListStyleCommand = editor.commands.get( 'listStyle' )!;

Expand All @@ -336,9 +369,7 @@ function createListPropertiesView( {
} );

// The command can be ListStyleCommand or DocumentListStyleCommand.
const isStyleTypeSupported = typeof listStyleCommand.isStyleTypeSupported == 'function' ?
( styleDefinition: StyleDefinition ) => listStyleCommand.isStyleTypeSupported( styleDefinition.type ) :
() => true;
const isStyleTypeSupported = getStyleTypeSupportChecker( listStyleCommand );

styleButtonViews = styleDefinitions.filter( isStyleTypeSupported ).map( styleButtonCreator );
}
Expand Down Expand Up @@ -386,91 +417,74 @@ function createListPropertiesView( {
* A helper that creates the list style submenu for menu bar.
*
* @param editor Editor instance.
* @param commandName Name of the list command.
* @param mainItemLabel Short list type name.
* @param defaultStyleLabel Label for `default` list style.
* @param styleDefinitions Array of avaialble styles for processed list type.
* @param propertiesConfig List properties configuration.
* @param parentCommandName Name of the list command.
* @param buttonLabel Label of the menu button.
* @param styleGridAriaLabel ARIA label of the styles grid.
* @param styleDefinitions Array of available styles for processed list type.
*/
function createMenuItem(
editor: Editor,
commandName: 'bulletedList' | 'numberedList',
mainItemLabel: string,
defaultStyleLabel: string,
styleDefinitions: Array<StyleDefinition>
function getMenuBarStylesMenuCreator(
{
editor,
propertiesConfig,
parentCommandName,
buttonLabel,
styleGridAriaLabel,
styleDefinitions
}: {
editor: Editor;
propertiesConfig: Readonly<ListPropertiesConfig>;
parentCommandName: 'bulletedList' | 'numberedList';
buttonLabel: string;
styleGridAriaLabel: string;
styleDefinitions: Array<StyleDefinition>;
}
) {
editor.ui.componentFactory.add( `menuBar:${ commandName }`, locale => {
return ( locale: Locale ) => {
const menuView = new MenuBarMenuView( locale );
const listCommand = editor.commands.get( commandName )!;
const listCommand = editor.commands.get( parentCommandName )!;
const listStyleCommand = editor.commands.get( 'listStyle' )!;
const listView = new MenuBarMenuListView( locale );

menuView.buttonView.set( {
label: mainItemLabel,
icon: icons[ commandName ]
const isStyleTypeSupported = getStyleTypeSupportChecker( listStyleCommand );
const styleButtonCreator = getStyleButtonCreator( {
editor,
parentCommandName,
listStyleCommand
} );

menuView.panelView.children.add( listView );

const options = [
{
tooltip: defaultStyleLabel,
type: 'default',
icon: icons[ commandName ]
const styleButtonViews = styleDefinitions.filter( isStyleTypeSupported ).map( styleButtonCreator );
const listPropertiesView = new ListPropertiesView( locale, {
styleGridAriaLabel,
enabledProperties: {
...propertiesConfig,

// Disable list start index and reversed in the menu bar.
startIndex: false,
reversed: false
},
...styleDefinitions
];

for ( const option of options ) {
const listItemView = new MenuBarMenuListItemView( locale, menuView );
const buttonView = new MenuBarMenuListItemButtonView( locale );

listItemView.children.add( buttonView );
listView.items.add( listItemView );

buttonView.set( {
label: option.tooltip,
// role: 'menuitemradio',
icon: option.icon
} );

buttonView.delegate( 'execute' ).to( menuView );

// Keep current list option highlighted.
listStyleCommand.on( 'change:value', () => {
buttonView.isOn = listStyleCommand.value === option.type &&
listCommand.value === true;
} );
listCommand.on( 'change:value', () => {
buttonView.isOn = listStyleCommand.value === option.type &&
listCommand.value === true;
} );

buttonView.on( 'execute', () => {
// If current list style is selected, execute main list command to remove list format.
if ( listStyleCommand.value == option.type ) {
editor.execute( commandName );
}
// For unique styles, just call `listStyle` command.
else if ( option.type != 'default' ) {
editor.execute( 'listStyle', { type: option.type } );
}
// For 'default' style we need to call main list command if slected element is not a list.
else if ( !listCommand.value ) {
editor.execute( commandName );
}
// Or change list style to 'default' if selected element is a list of custom style.
else {
editor.execute( 'listStyle', { type: option.type } );
}
styleButtonViews
} );

editor.editing.view.focus();
} );
}
listPropertiesView.delegate( 'execute' ).to( menuView );

menuView.buttonView.set( {
label: buttonLabel,
icon: icons[ parentCommandName ]
} );
menuView.panelView.children.add( listPropertiesView );
menuView.bind( 'isEnabled' ).to( listCommand, 'isEnabled' );
menuView.on( 'execute', () => {
editor.editing.view.focus();
} );

return menuView;
} );
};
}

function getStyleTypeSupportChecker( listStyleCommand: LegacyListStyleCommand | ListStyleCommand ) {
if ( typeof listStyleCommand.isStyleTypeSupported == 'function' ) {
return ( styleDefinition: StyleDefinition ) => listStyleCommand.isStyleTypeSupported( styleDefinition.type );
} else {
return () => true;
}
}

interface StyleDefinition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ describe( 'ListPropertiesUI', () => {

styleButtonView.fire( 'execute' );

sinon.assert.calledWithExactly( editor.execute, 'listStyle', { type: 'default' } );
sinon.assert.calledWithExactly( editor.execute, 'bulletedList' );
sinon.assert.calledOnce( editor.editing.view.focus );
sinon.assert.callOrder( editor.execute, editor.editing.view.focus );
} );
Expand Down Expand Up @@ -728,7 +728,7 @@ describe( 'ListPropertiesUI', () => {

styleButtonView.fire( 'execute' );

sinon.assert.calledWithExactly( editor.execute, 'listStyle', { type: 'default' } );
sinon.assert.calledWithExactly( editor.execute, 'numberedList' );
sinon.assert.calledOnce( editor.editing.view.focus );
sinon.assert.callOrder( editor.execute, editor.editing.view.focus );
} );
Expand Down

0 comments on commit 04674e8

Please sign in to comment.