Skip to content

Commit

Permalink
Merge pull request #14174 from ckeditor/ck/12738-add-documentlistsepa…
Browse files Browse the repository at this point in the history
…rator-plugin

Feature (list): Add the `AdjacentListsSupport` plugin to separate lists of the same type. Closes #12738.
  • Loading branch information
niegowski committed May 23, 2023
2 parents 39c2d91 + 42a5384 commit 574c934
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/ckeditor5-list/docs/features/document-lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ ClassicEditor
The {@link module:list/documentlistproperties~DocumentListProperties} feature overrides UI button implementations from the {@link module:list/list/listui~ListUI}.
</info-box>

## List merging

By default, two lists of the same type (ordered and unordered) that are next to each other are merged together. This is done so that lists that visually appear to be one continuous list actually are, even if the user has accidentally created several of them.

Unfortunately, in some cases this can be undesirable behavior. For example, two adjacent numbered lists, each with two items, will merge into a single list with the numbers 1 through 4.

To prevent this behavior, enable the `AdjacentListsSupport` plugin.

```js
import AdjacentListsSupport from '@ckeditor/ckeditor5-list/src/documentlist/adjacentlistssupport.js';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [
AdjacentListsSupport,
/* Other plugins */
],
} )
.then( /* ... */ )
.catch( /* ... */ );
```

## Related features

These features also provide similar functionality:
Expand Down
2 changes: 2 additions & 0 deletions packages/ckeditor5-list/src/augmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
DocumentListProperties,
DocumentListPropertiesEditing,
DocumentListPropertiesUtils,
AdjacentListsSupport,
DocumentListUtils,
ListConfig,
List,
Expand Down Expand Up @@ -56,6 +57,7 @@ declare module '@ckeditor/ckeditor5-core' {
[ DocumentListPropertiesEditing.pluginName ]: DocumentListPropertiesEditing;
[ DocumentListPropertiesUtils.pluginName ]: DocumentListPropertiesUtils;
[ DocumentListUtils.pluginName ]: DocumentListUtils;
[ AdjacentListsSupport.pluginName ]: AdjacentListsSupport;
[ List.pluginName ]: List;
[ ListEditing.pluginName ]: ListEditing;
[ ListProperties.pluginName ]: ListProperties;
Expand Down
110 changes: 110 additions & 0 deletions packages/ckeditor5-list/src/documentlist/adjacentlistssupport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module list/documentlist/adjacentlistssupport
*/

import type { GetCallback } from 'ckeditor5/src/utils';
import { Plugin } from 'ckeditor5/src/core';

import type { UpcastElementEvent, ViewElement } from 'ckeditor5/src/engine';

export default class AdjacentListsSupport extends Plugin {
/**
* @inheritDoc
*/
public static get pluginName(): 'AdjacentListsSupport' {
return 'AdjacentListsSupport';
}

/**
* @inheritDoc
*/
public init(): void {
const editor = this.editor;
const model = editor.model;

model.schema.register( 'listSeparator', {
allowWhere: '$block',
isBlock: true
} );

editor.conversion.for( 'upcast' )
// Add `listSeparator` element between similar list elements on upcast.
.add( dispatcher => {
dispatcher.on<UpcastElementEvent>( 'element:ol', listSeparatorUpcastConverter() );
dispatcher.on<UpcastElementEvent>( 'element:ul', listSeparatorUpcastConverter() );
} )
// View to model transformation.
.elementToElement( {
model: 'listSeparator',
view: 'ck-list-separator'
} );

// The `listSeparator` element should exist in the view, but should be invisible (hidden).
editor.conversion.for( 'editingDowncast' ).elementToElement( {
model: 'listSeparator',
view: {
name: 'div',
classes: [ 'ck-list-separator', 'ck-hidden' ]
}
} );

// The `listSeparator` element should not exist in output data.
editor.conversion.for( 'dataDowncast' ).elementToElement( {
model: 'listSeparator',
view: ( modelElement, conversionApi ) => {
const viewElement = conversionApi.writer.createContainerElement( 'ck-list-separator' );

conversionApi.writer.setCustomProperty( 'dataPipeline:transparentRendering', true, viewElement );

viewElement.getFillerOffset = () => null;

return viewElement;
}
} );
}
}

/**
* Inserts a `listSeparator` element between two lists of the same type (`ol` + `ol` or `ul` + `ul`).
*/
function listSeparatorUpcastConverter(): GetCallback<UpcastElementEvent> {
return ( evt, data, conversionApi ) => {
const element: ViewElement = data.viewItem;
const nextSibling = element.nextSibling as ViewElement | null;

if ( !nextSibling ) {
return;
}

if ( element.name !== nextSibling.name ) {
return;
}

if ( !data.modelRange ) {
Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
}

const writer = conversionApi.writer;
const modelElement = writer.createElement( 'listSeparator' );

// Try to insert `listSeparator` element on the current model cursor position.
if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) {
return;
}

const parts = conversionApi.getSplitParts( modelElement );

// Extend model range with the range of the created listSeparator element.
data.modelRange = writer.createRange(
data.modelRange!.start,
writer.createPositionAfter( parts[ parts.length - 1 ] )
);

conversionApi.updateConversionResult( modelElement, data );
};
}
1 change: 1 addition & 0 deletions packages/ckeditor5-list/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export { default as DocumentList } from './documentlist';
export { default as DocumentListEditing, type DocumentListEditingPostFixerEvent } from './documentlist/documentlistediting';
export { default as DocumentListIndentCommand } from './documentlist/documentlistindentcommand';
export { default as AdjacentListsSupport } from './documentlist/adjacentlistssupport';
export { default as DocumentListProperties } from './documentlistproperties';
export { default as DocumentListPropertiesEditing } from './documentlistproperties/documentlistpropertiesediting';
export { default as DocumentListUtils } from './documentlist/documentlistutils';
Expand Down

0 comments on commit 574c934

Please sign in to comment.