Skip to content

Commit

Permalink
Add list integration.
Browse files Browse the repository at this point in the history
  • Loading branch information
mremiszewski committed Feb 28, 2024
1 parent 2a06f3b commit 3e907b7
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 86 deletions.
7 changes: 4 additions & 3 deletions packages/ckeditor5-list/src/list/listui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
* @module list/list/listui
*/

import { createUIComponent } from './utils.js';
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 All @@ -30,7 +31,7 @@ export default class ListUI extends Plugin {
const t = this.editor.t;

// Create two buttons and link them with numberedList and bulletedList commands.
createUIComponent( this.editor, 'numberedList', t( 'Numbered List' ), icons.numberedList );
createUIComponent( this.editor, 'bulletedList', t( 'Bulleted List' ), icons.bulletedList );
createUIComponents( this.editor, 'numberedList', t( 'Numbered List' ), icons.numberedList );
createUIComponents( this.editor, 'bulletedList', t( 'Bulleted List' ), icons.bulletedList );
}
}
54 changes: 38 additions & 16 deletions packages/ckeditor5-list/src/list/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,65 @@
*/

import type { Editor } from 'ckeditor5/src/core.js';
import { ButtonView, type ButtonExecuteEvent } from 'ckeditor5/src/ui.js';
import { ButtonView, MenuBarMenuListItemButtonView, type ButtonExecuteEvent } from 'ckeditor5/src/ui.js';

/**
* Helper method for creating a UI button and linking it with an appropriate command.
* Helper method for creating toolbar and menu buttons and linking them with an appropriate command.
*
* @internal
* @param editor The editor instance to which the UI component will be added.
* @param commandName The name of the command.
* @param label The button label.
* @param icon The source of the icon.
*/
export function createUIComponent(
export function createUIComponents(
editor: Editor,
commandName: 'bulletedList' | 'numberedList' | 'todoList',
label: string,
icon: string
): void {
editor.ui.componentFactory.add( commandName, locale => {
const command = editor.commands.get( commandName )!;
const buttonView = new ButtonView( locale );
editor.ui.componentFactory.add( commandName, () => {
const buttonView = _createButton( ButtonView, editor, commandName, label, icon );

buttonView.set( {
label,
icon,
tooltip: true,
isToggleable: true
} );

// Bind button model to command.
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
return buttonView;
} );

// Execute command.
buttonView.on<ButtonExecuteEvent>( 'execute', () => {
editor.execute( commandName );
editor.editing.view.focus();
} );
editor.ui.componentFactory.add( `menuBar:${ commandName }`, () =>
_createButton( MenuBarMenuListItemButtonView, editor, commandName, label, icon )
);
}

return buttonView;
/**
* Creates a button to use either in toolbar or in menu bar.
*/
function _createButton<T extends typeof ButtonView | typeof MenuBarMenuListItemButtonView>(
ButtonClass: T,
editor: Editor,
commandName: 'bulletedList' | 'numberedList' | 'todoList',
label: string,
icon: string
): InstanceType<T> {
const command = editor.commands.get( commandName )!;
const view = new ButtonClass( editor.locale ) as InstanceType<T>;

view.set( {
label,
icon
} );

// Bind button model to command.
view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );

// Execute the command.
view.on<ButtonExecuteEvent>( 'execute', () => {
editor.execute( commandName );
editor.editing.view.focus();
} );

return view;
}
239 changes: 174 additions & 65 deletions packages/ckeditor5-list/src/listproperties/listpropertiesui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
SplitButtonView,
createDropdown,
focusChildOnDropdownOpen,
type DropdownView
type DropdownView,
MenuBarMenuView,
MenuBarMenuListView,
MenuBarMenuListItemView,
MenuBarMenuListItemButtonView
} from 'ckeditor5/src/ui.js';

import type { Locale } from 'ckeditor5/src/utils.js';
Expand Down Expand Up @@ -64,84 +68,98 @@ export default class ListPropertiesUI extends Plugin {
// 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 ) {
editor.ui.componentFactory.add( 'bulletedList', getDropdownViewCreator( {
const styleDefinitions = [
{
label: t( 'Toggle the disc list style' ),
tooltip: t( 'Disc' ),
type: 'disc',
icon: listStyleDiscIcon
},
{
label: t( 'Toggle the circle list style' ),
tooltip: t( 'Circle' ),
type: 'circle',
icon: listStyleCircleIcon
},
{
label: t( 'Toggle the square list style' ),
tooltip: t( 'Square' ),
type: 'square',
icon: listStyleSquareIcon
}
];
const buttonLabel = t( 'Bulleted list' );
const commandName = 'bulletedList';

editor.ui.componentFactory.add( commandName, getDropdownViewCreator( {
editor,
parentCommandName: 'bulletedList',
buttonLabel: t( 'Bulleted List' ),
parentCommandName: commandName,
buttonLabel,
buttonIcon: icons.bulletedList,
styleGridAriaLabel: t( 'Bulleted list styles toolbar' ),
styleDefinitions: [
{
label: t( 'Toggle the disc list style' ),
tooltip: t( 'Disc' ),
type: 'disc',
icon: listStyleDiscIcon
},
{
label: t( 'Toggle the circle list style' ),
tooltip: t( 'Circle' ),
type: 'circle',
icon: listStyleCircleIcon
},
{
label: t( 'Toggle the square list style' ),
tooltip: t( 'Square' ),
type: 'square',
icon: listStyleSquareIcon
}
]
styleDefinitions
} ) );

// Add menu item for bulleted list.
createMenuItem( editor, commandName, buttonLabel, t( 'Bulleted' ), 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 ) {
editor.ui.componentFactory.add( 'numberedList', getDropdownViewCreator( {
const styleDefinitions = [
{
label: t( 'Toggle the decimal list style' ),
tooltip: t( 'Decimal' ),
type: 'decimal',
icon: listStyleDecimalIcon
},
{
label: t( 'Toggle the decimal with leading zero list style' ),
tooltip: t( 'Decimal with leading zero' ),
type: 'decimal-leading-zero',
icon: listStyleDecimalWithLeadingZeroIcon
},
{
label: t( 'Toggle the lower–roman list style' ),
tooltip: t( 'Lower–roman' ),
type: 'lower-roman',
icon: listStyleLowerRomanIcon
},
{
label: t( 'Toggle the upper–roman list style' ),
tooltip: t( 'Upper-roman' ),
type: 'upper-roman',
icon: listStyleUpperRomanIcon
},
{
label: t( 'Toggle the lower–latin list style' ),
tooltip: t( 'Lower-latin' ),
type: 'lower-latin',
icon: listStyleLowerLatinIcon
},
{
label: t( 'Toggle the upper–latin list style' ),
tooltip: t( 'Upper-latin' ),
type: 'upper-latin',
icon: listStyleUpperLatinIcon
}
];
const buttonLabel = t( 'Numbered list' );
const commandName = 'numberedList';

editor.ui.componentFactory.add( commandName, getDropdownViewCreator( {
editor,
parentCommandName: 'numberedList',
buttonLabel: t( 'Numbered List' ),
parentCommandName: commandName,
buttonLabel,
buttonIcon: icons.numberedList,
styleGridAriaLabel: t( 'Numbered list styles toolbar' ),
styleDefinitions: [
{
label: t( 'Toggle the decimal list style' ),
tooltip: t( 'Decimal' ),
type: 'decimal',
icon: listStyleDecimalIcon
},
{
label: t( 'Toggle the decimal with leading zero list style' ),
tooltip: t( 'Decimal with leading zero' ),
type: 'decimal-leading-zero',
icon: listStyleDecimalWithLeadingZeroIcon
},
{
label: t( 'Toggle the lower–roman list style' ),
tooltip: t( 'Lower–roman' ),
type: 'lower-roman',
icon: listStyleLowerRomanIcon
},
{
label: t( 'Toggle the upper–roman list style' ),
tooltip: t( 'Upper-roman' ),
type: 'upper-roman',
icon: listStyleUpperRomanIcon
},
{
label: t( 'Toggle the lower–latin list style' ),
tooltip: t( 'Lower-latin' ),
type: 'lower-latin',
icon: listStyleLowerLatinIcon
},
{
label: t( 'Toggle the upper–latin list style' ),
tooltip: t( 'Upper-latin' ),
type: 'upper-latin',
icon: listStyleUpperLatinIcon
}
]
styleDefinitions
} ) );

// Add menu item for numbered list.
createMenuItem( editor, commandName, buttonLabel, t( 'Numbered' ), styleDefinitions );
}
}
}
Expand Down Expand Up @@ -364,6 +382,97 @@ function createListPropertiesView( {
return listPropertiesView;
}

/**
* 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.
*/
function createMenuItem(
editor: Editor,
commandName: 'bulletedList' | 'numberedList',
mainItemLabel: string,
defaultStyleLabel: string,
styleDefinitions: Array<StyleDefinition>
) {
editor.ui.componentFactory.add( `menuBar:${ commandName }`, locale => {
const menuView = new MenuBarMenuView( locale );
const listCommand = editor.commands.get( commandName )!;
const listStyleCommand = editor.commands.get( 'listStyle' )!;
const listView = new MenuBarMenuListView( locale );

menuView.buttonView.set( {
label: mainItemLabel,
icon: icons[ commandName ]
} );

menuView.panelView.children.add( listView );

const options = [
{
tooltip: defaultStyleLabel,
type: 'default',
icon: icons[ commandName ]
},
...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 } );
}

editor.editing.view.focus();
} );
}

menuView.bind( 'isEnabled' ).to( listCommand, 'isEnabled' );

return menuView;
} );
}

interface StyleDefinition {

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/ckeditor5-list/src/todolist/todolistui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @module list/todolist/todolistui
*/

import { createUIComponent } from '../list/utils.js';
import { createUIComponents } from '../list/utils.js';
import { icons, Plugin } from 'ckeditor5/src/core.js';

/**
Expand All @@ -28,6 +28,6 @@ export default class TodoListUI extends Plugin {
public init(): void {
const t = this.editor.t;

createUIComponent( this.editor, 'todoList', t( 'To-do List' ), icons.todoList );
createUIComponents( this.editor, 'todoList', t( 'To-do List' ), icons.todoList );
}
}
Loading

0 comments on commit 3e907b7

Please sign in to comment.