Skip to content

Commit

Permalink
Merge pull request #11503 from ckeditor/ck/epic/2973-document-lists
Browse files Browse the repository at this point in the history
Feature (list): Introducing the document (advanced) list feature (multiple blocks per list item). Closes #2973. Closes #10812.

Feature (list): Introducing the document (advanced) list properties feature (list styles, start index, reversed list). Closes #11065.

Feature (html-support): Adds support for document list in the `GeneralHtmlSupport` feature. Closes #11454. Closes #11359. Closes #11358.

Feature (engine): Added a new `Model#insertObject()` method for inserting elements defined as objects by model schema (see #11198).

Feature (engine): Introduced inheritable `$container`, `$blockObject`, and `$inlineObject` element types in the model `Schema` (see #11197).

Feature (engine): Introduced `TabObserver` that allows listening to pressing down the `Tab` key in a specified context.

Feature (paragraph): Added an optional `options.attributes` parameter to the `InsertParagraph` command that allows setting attributes on a created paragraph (see #11198).

Feature (core): `MultiCommand` now allows setting a priority (order) of registered sub commands. Closes #11083.

Feature (engine): Added a new `Schema#getAttributesWithProperty()` method that retrieves attributes from a node which have given property (see #11198).

Feature (engine): Added a new `Schema#setAllowedAttributes()` method that validates whether attributes are allowed on a given element before setting them (see #11198).

Other (engine): The `Differ` change entries for `insert` and `remove` types are extended with a map of attributes that were set while inserting an element or that belonged to an element that got removed.

Other (engine): The `DowncastHelpers` are passing an additional parameter to the creator functions (the `data` that provides more context to the element creator callback).

Other (engine): The `isAllowedInsideAttributeElement` option was removed, from now on `AttributeElements` are allowed to wrap any view element.

Other (engine): The `ConversionApi` provided by the `UpcastDispatcher` was extended by an additional `keepEmptyElement()` method that marks an element that was created during splitting a model element that should not get removed on conversion even if it is empty. 

Other (html-support): Updated default schema definitions for various elements taking advantage of the `$container`, `$blockObject`, and `$inlineObject` elements in model schema (see #11197).

Other (table, code-block, list): The handling of `Tab` and `Shift+Tab` keystrokes switched to the `'tab'` view document event and now respects the event context.

Other (media-embed): Added an optional `findOptimalPosition` parameter to the `insertMedia()` helper that allows for inserting `media` model element without breaking the content (see #11198).

Fix (link): The link decorators should be converted on block images only once (should not wrap block image with an additional link).

Internal (engine): Added option for the `DomConverter` to transparently render only the content of the element in the data pipeline.

Internal (engine): Added option for the `DomConverter` to transparently render only the content of the element in the data pipeline.

Internal (engine): The `findOptimalPosition()` helper is now available in the ckeditor5-engine package for internal use (see #11198).

Internal (list): `DocumentListEditing` should extend `$container` (for `blockQuote`, etc.), `$block` (for `paragraph`, `heading2`, etc.), and `$blockObject` (for `table`, `horizontalLine`, etc.) with its attributes. Closes #11197.

Internal (list): `indentList` and `outdentList` commands are now registered with priority in 'Indent' `MultiCommand` , `Tab` and `Tab+Shift` listeners now executes in `li` context in order to not interfere with other plugins' listeners . Closes #11072.

Internal (list): Adds `DocumentListStartCommand` and `DocumentListReversedCommand`. Closes #11166.

Internal (list): Adds `DocumentListStyleCommand`. See #11166.

Internal (list): Adds post-fixer that makes sure that all items in a single list have the same start, reversed, and style properties. Closes #11167.

Internal (list): Deleting a widget which is a document list item should be possible. Closes #11346.

Internal (list): Document list items should not get split by inserting block objects (widgets). Closes #11198.

Internal (list): Implemented backspace and delete handling in and around document lists. Closes #10878.

Internal (list): Implemented handling of `Tab` and `Tab+Shift` keys in document lists. Closes #10880.

Internal (list): Implemented the `DocumentListMergeCommand`. Closes #10977.

Internal (list): Improved enter handling in document lists by allowing to split the list item when the collapsed selection is anchored in the first (but not only) empty block of a list item (see #10879, #10976).

Internal (list): Integrated the enter feature with document lists. Closes #10879. Closes #10976.

Internal (list): Reset document list properties after indent. Closes #11357.

Internal (table, page-break, horizontal-line, media-embed, html-embed, image): `table`, `pageBreak`, `horizontalLine`, `media`, `imageBlock`,`imageInline` elements are now inserted with `insertObject()` function instead of `insertContent()` (see #11198).

Internal (list): Added `DocumentListCommand` and `DocumentListIndentCommand`. Closes #10974. Closes #10975.

MINOR BREAKING CHANGE (html-support): The `$htmlSection`, `$htmlObjectBlock`, and `$htmlObjectInline` element types are no longer available for custom elements registered via `registerBlockElement()` to inherit from. Please use `$container`, `$blockObject`, and `$inlineObject` instead (see #11197).

MINOR BREAKING CHANGE (engine): The `isAllowedInsideAttributeElement` option was removed so `AttributeElements` can wrap any view element (according to positions). Make sure that you are not wrapping any `ContainerElement` by an accident by not checking the target in the converter. Those would previously get wrapped by an `AttributeElement` that immediately would be removed by the `ContainerElement` within it so there would not be any visual effect.

MINOR BREAKING CHANGE (engine): The handling of `Tab` and `Shift+Tab` keystrokes switched to the `'tab'` view document event across the project. If your integration uses `KeystrokeHandler` for `Tab` key handling, we recommend you migrate to the `'tab'` event to avoid unpredicted errors.

MINOR BREAKING CHANGE (engine): If your integration uses `Model#insertContent()` and `findOptimalInsertionRange()` to insert widgets into the content, we recommend you migrate your code to `Model#insertObject()` for best results. This is particularly relevant for compatibility with the document (advanced) lists feature (see #11198).
  • Loading branch information
oleq authored Apr 4, 2022
2 parents d468faa + c686546 commit b53d2a4
Show file tree
Hide file tree
Showing 183 changed files with 46,256 additions and 651 deletions.
2 changes: 0 additions & 2 deletions docs/_snippets/framework/tutorials/inline-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,6 @@ class PlaceholderEditing extends Plugin {

const placeholderView = viewWriter.createContainerElement( 'span', {
class: 'placeholder'
}, {
isAllowedInsideAttributeElement: true
} );

// Insert the placeholder name (as a text).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,6 @@ export default class PlaceholderEditing extends Plugin {

const placeholderView = viewWriter.createContainerElement( 'span', {
class: 'placeholder'
}, {
isAllowedInsideAttributeElement: true
} );

// Insert the placeholder name (as a text).
Expand Down Expand Up @@ -944,8 +942,6 @@ class PlaceholderEditing extends Plugin {

const placeholderView = viewWriter.createContainerElement( 'span', {
class: 'placeholder'
}, {
isAllowedInsideAttributeElement: true
} );

// Insert the placeholder name (as a text).
Expand Down
3 changes: 1 addition & 2 deletions packages/ckeditor5-block-quote/src/blockquoteediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export default class BlockQuoteEditing extends Plugin {
editor.commands.add( 'blockQuote', new BlockQuoteCommand( editor ) );

schema.register( 'blockQuote', {
allowWhere: '$block',
allowContentOf: '$root'
inheritAllFrom: '$container'
} );

editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
Expand Down
8 changes: 8 additions & 0 deletions packages/ckeditor5-block-quote/tests/blockquoteediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ describe( 'BlockQuoteEditing', () => {
expect( model.schema.checkChild( [ '$root', 'blockQuote' ], 'foo' ) ).to.be.false;
} );

it( 'inherits attributes from $container', () => {
model.schema.extend( '$container', {
allowAttributes: 'foo'
} );

expect( model.schema.checkAttribute( 'blockQuote', 'foo' ) ).to.be.true;
} );

it( 'adds converters to the data pipeline', () => {
const data = '<blockquote><p>x</p></blockquote>';

Expand Down
1 change: 1 addition & 0 deletions packages/ckeditor5-code-block/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@ckeditor/ckeditor5-editor-classic": "^33.0.0",
"@ckeditor/ckeditor5-image": "^33.0.0",
"@ckeditor/ckeditor5-indent": "^33.0.0",
"@ckeditor/ckeditor5-list": "^33.0.0",
"@ckeditor/ckeditor5-markdown-gfm": "^33.0.0",
"@ckeditor/ckeditor5-paragraph": "^33.0.0",
"@ckeditor/ckeditor5-theme-lark": "^33.0.0",
Expand Down
41 changes: 28 additions & 13 deletions packages/ckeditor5-code-block/src/codeblockediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default class CodeBlockEditing extends Plugin {
const schema = editor.model.schema;
const model = editor.model;
const view = editor.editing.view;
const isDocumentListEditingLoaded = editor.plugins.has( 'DocumentListEditing' );

const normalizedLanguagesDefs = getNormalizedAndLocalizedLanguageDefinitions( editor );

Expand All @@ -97,19 +98,20 @@ export default class CodeBlockEditing extends Plugin {
editor.commands.add( 'indentCodeBlock', new IndentCodeBlockCommand( editor ) );
editor.commands.add( 'outdentCodeBlock', new OutdentCodeBlockCommand( editor ) );

const getCommandExecuter = commandName => {
return ( data, cancel ) => {
const command = this.editor.commands.get( commandName );
this.listenTo( view.document, 'tab', ( evt, data ) => {
const commandName = data.shiftKey ? 'outdentCodeBlock' : 'indentCodeBlock';
const command = editor.commands.get( commandName );

if ( command.isEnabled ) {
this.editor.execute( commandName );
cancel();
}
};
};
if ( !command.isEnabled ) {
return;
}

editor.keystrokes.set( 'Tab', getCommandExecuter( 'indentCodeBlock' ) );
editor.keystrokes.set( 'Shift+Tab', getCommandExecuter( 'outdentCodeBlock' ) );
editor.execute( commandName );

data.stopPropagation();
data.preventDefault();
evt.stop();
}, { context: 'pre' } );

schema.register( 'codeBlock', {
allowWhere: '$block',
Expand All @@ -118,8 +120,17 @@ export default class CodeBlockEditing extends Plugin {
allowAttributes: [ 'language' ]
} );

// Allow all list* attributes on `codeBlock` (integration with DocumentList).
// Disallow all attributes on $text inside `codeBlock`.
schema.addAttributeCheck( context => {
schema.addAttributeCheck( ( context, attributeName ) => {
const isDocumentListAttributeOnCodeBlock = context.endsWith( 'codeBlock' ) &&
attributeName.startsWith( 'list' ) &&
attributeName !== 'list';

if ( isDocumentListEditingLoaded && isDocumentListAttributeOnCodeBlock ) {
return true;
}

if ( context.endsWith( 'codeBlock $text' ) ) {
return false;
}
Expand Down Expand Up @@ -210,7 +221,11 @@ export default class CodeBlockEditing extends Plugin {
const outdent = commands.get( 'outdent' );

if ( indent ) {
indent.registerChildCommand( commands.get( 'indentCodeBlock' ) );
// Priority is highest due to integration with `IndentList` command of `List` plugin.
// If selection is in a code block we give priority to it. This way list item cannot be indented
// but if we would give priority to indenting list item then user would have to indent list item
// as much as possible and only then he could indent code block.
indent.registerChildCommand( commands.get( 'indentCodeBlock' ), { priority: 'highest' } );
}

if ( outdent ) {
Expand Down
71 changes: 70 additions & 1 deletion packages/ckeditor5-code-block/tests/codeblock-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import GFMDataProcessor from '@ckeditor/ckeditor5-markdown-gfm/src/gfmdataprocessor';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ImageInlineEditing from '@ckeditor/ckeditor5-image/src/image/imageinlineediting';
import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import DocumentListEditing from '@ckeditor/ckeditor5-list/src/documentlist/documentlistediting';
import { setData, getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

import CodeBlockUI from '../src/codeblockui';
import CodeBlockEditing from '../src/codeblockediting';
Expand Down Expand Up @@ -154,4 +155,72 @@ describe( 'CodeBlock - integration', () => {
expect( getData( editor.model ) ).to.equal( '<codeBlock language="cs">[]</codeBlock>' );
} );
} );

describe( 'with DocumentListEditing', () => {
let editor, model;

describe( 'when DocumentListEditing is loaded', () => {
beforeEach( async () => {
editor = await ClassicTestEditor
.create( '', {
plugins: [ CodeBlockEditing, DocumentListEditing, Enter, Paragraph ]
} );

model = editor.model;
} );

afterEach( async () => {
await editor.destroy();
} );

it( 'should allow all attributes starting with list* in the schema', () => {
setData( model, '<codeBlock language="plaintext">[]foo</codeBlock>' );

const codeBlock = model.document.getRoot().getChild( 0 );

expect( model.schema.checkAttribute( codeBlock, 'listItemId' ), 'listItemId' ).to.be.true;
expect( model.schema.checkAttribute( codeBlock, 'listType' ), 'listType' ).to.be.true;
expect( model.schema.checkAttribute( codeBlock, 'listStart' ), 'listStart' ).to.be.true;
expect( model.schema.checkAttribute( codeBlock, 'listFoo' ), 'listFoo' ).to.be.true;
} );

it( 'should disallow attributes that do not start with "list" in the schema but include the sequence', () => {
setData( model, '<codeBlock language="plaintext">[]foo</codeBlock>' );

const codeBlock = model.document.getRoot().getChild( 0 );

expect( model.schema.checkAttribute( codeBlock, 'list' ), 'list' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'fooList' ), 'fooList' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'alist' ), 'alist' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'alistb' ), 'alistb' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'LISTbar' ), 'LISTbar' ).to.be.false;
} );
} );

describe( 'when DocumentListEditing is not loaded', () => {
beforeEach( async () => {
editor = await ClassicTestEditor
.create( '', {
plugins: [ CodeBlockEditing, Enter, Paragraph ]
} );

model = editor.model;
} );

afterEach( async () => {
await editor.destroy();
} );

it( 'should disallow all attributes starting with list* in the schema', () => {
setData( model, '<codeBlock language="plaintext">[]foo</codeBlock>' );

const codeBlock = model.document.getRoot().getChild( 0 );

expect( model.schema.checkAttribute( codeBlock, 'listItemId' ), 'listItemId' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'listType' ), 'listType' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'listStart' ), 'listStart' ).to.be.false;
expect( model.schema.checkAttribute( codeBlock, 'listFoo' ), 'listFoo' ).to.be.false;
} );
} );
} );
} );
Loading

0 comments on commit b53d2a4

Please sign in to comment.