Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowed easy integration of blocks, containers, and block objects with DocumentLists through schema #11250

Merged
merged 12 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "^31.1.0",
"@ckeditor/ckeditor5-image": "^31.1.0",
"@ckeditor/ckeditor5-indent": "^31.1.0",
"@ckeditor/ckeditor5-list": "^31.1.0",
"@ckeditor/ckeditor5-markdown-gfm": "^31.1.0",
"@ckeditor/ckeditor5-paragraph": "^31.1.0",
"@ckeditor/ckeditor5-theme-lark": "^31.1.0",
Expand Down
15 changes: 12 additions & 3 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 Down Expand Up @@ -115,12 +116,20 @@ export default class CodeBlockEditing extends Plugin {
allowWhere: '$block',
allowChildren: '$text',
isBlock: true,
allowAttributes: [ 'language' ],
allowAttributesOf: '$container'
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
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;
} );
} );
} );
} );
8 changes: 0 additions & 8 deletions packages/ckeditor5-code-block/tests/codeblockediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,6 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model ) ).to.equal( '<codeBlock language="css">f[o]o</codeBlock>' );
} );

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

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

describe( 'tab key handling', () => {
let domEvtDataStub;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ Here is a table listing various model elements and their properties registered i
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
</tr>
<tr>
<td><code>$container</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_negative"><code>false</code></td>
</tr>
<tr>
<td><code>$blockObject</code></td>
<td class="value_positive"><code>true</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited1"><sup>[1]</sup></a></td>
<td class="value_positive"><code>true</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited2"><sup>[2]</sup></a></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited3"><sup>[3]</sup></a></td>
</tr>
<tr>
<td><code>$inlineObject</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited1"><sup>[1]</sup></a></td>
<td class="value_positive"><code>true</code></td>
<td class="value_positive"><code>true</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited2"><sup>[2]</sup></a></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited3"><sup>[3]</sup></a></td>
</tr>
<tr>
<td><code>$clipboardHolder</code></td>
<td class="value_negative"><code>false</code></td>
Expand Down Expand Up @@ -202,7 +229,7 @@ Here is a table listing various model elements and their properties registered i
</tr>
<tr>
<td><code>horizontalLine</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_positive"><code>true</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited1"><sup>[1]</sup></a></td>
<td class="value_positive"><code>true</code></td>
<td class="value_negative"><code>false</code></td>
Expand Down Expand Up @@ -247,7 +274,7 @@ Here is a table listing various model elements and their properties registered i
</tr>
<tr>
<td><code>pageBreak</code></td>
<td class="value_negative"><code>false</code></td>
<td class="value_positive"><code>true</code></td>
<td class="value_positive_inherited"><code>true</code><a href="#inherited1"><sup>[1]</sup></a></td>
<td class="value_positive"><code>true</code></td>
<td class="value_negative"><code>false</code></td>
Expand Down Expand Up @@ -409,19 +436,39 @@ At the same time, elements like paragraphs, list items, or headings **are not**

## Generic items

There are three basic generic items: `$root`, `$block` and `$text`. They are defined as follows:
There are several generic items (classes of elements) available: `$root`, `$container`, `$block`, `$blockObject`, `$inlineObject`, and `$text`. They are defined as follows:

```js
schema.register( '$root', {
isLimit: true
} );

schema.register( '$container', {
allowIn: [ '$root', '$container' ]
} );

schema.register( '$block', {
allowIn: '$root',
allowIn: [ '$root', '$container' ],
isBlock: true
} );

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

schema.register( '$inlineObject', {
allowWhere: '$text',
allowAttributesOf: '$text',
isInline: true,
isObject: true
} );

schema.register( '$text', {
allowIn: '$block',
isInline: true
isInline: true,
isContent: true
} );
```

Expand Down Expand Up @@ -455,12 +502,13 @@ Thanks to the fact that the `<paragraph>` definition is inherited from `<$block>

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

Thanks to that, despite the fact that block quote and paragraph features know nothing about themselves, paragraphs will be allowed in block quotes and block quotes will be allowed in all places where blocks are allowed. So if anyone registers a `<section>` element (with the `allowContentOf: '$root'` rule), that `<section>` elements will allow block quotes, too.
Because `<$block>` is allowed in `<$container>` (see `schema.register( '$block' ...)`), despite the fact that block quote and paragraph features know nothing about each other, paragraphs will be allowed in block quotes: schema rules allow chaining.

Taking this even further, if anyone registers a `<section>` element (with the `allowContentOf: '$root'` rule), because `<$container>` is also allowed in `<$root>` (see `schema.register( '$container' ...)`) `<section>` elements will allow block quotes out–of–the–box.

<info-box>
You can read more about the format of the item definition in {@link module:engine/model/schema~SchemaItemDefinition}.
Expand Down
20 changes: 16 additions & 4 deletions packages/ckeditor5-engine/src/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,33 @@ export default class Model {

// Register some default abstract entities.
this.schema.register( '$root', {
allowContentOf: '$container',
isLimit: true
} );

this.schema.register( '$container', {
allowChildren: [ '$block', '$container' ]
allowIn: [ '$root', '$container' ]
} );

this.schema.register( '$block', {
allowChildren: '$text',
allowAttributesOf: '$container',
allowIn: [ '$root', '$container' ],
isBlock: true
} );

this.schema.register( '$blockObject', {
allowWhere: '$block',
isBlock: true,
isObject: true
} );

this.schema.register( '$inlineObject', {
allowWhere: '$text',
allowAttributesOf: '$text',
isInline: true,
isObject: true
} );

this.schema.register( '$text', {
allowIn: '$block',
isInline: true,
isContent: true
} );
Expand Down
44 changes: 34 additions & 10 deletions packages/ckeditor5-engine/src/model/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -1135,19 +1135,39 @@ mix( Schema, ObservableMixin );
*
* # Generic items
*
* There are three basic generic items: `$root`, `$block` and `$text`.
* They are defined as follows:
* There are several generic items (classes of elements) available: `$root`, `$container`, `$block`, `$blockObject`,
* `$inlineObject`, and `$text`. They are defined as follows:
*
* this.schema.register( '$root', {
* schema.register( '$root', {
* isLimit: true
* } );
* this.schema.register( '$block', {
* allowIn: '$root',
*
* schema.register( '$container', {
* allowIn: [ '$root', '$container' ]
* } );
*
* schema.register( '$block', {
* allowIn: [ '$root', '$container' ],
* isBlock: true
* } );
* this.schema.register( '$text', {
*
* schema.register( '$blockObject', {
* allowWhere: '$block',
* isBlock: true,
* isObject: true
* } );
*
* schema.register( '$inlineObject', {
* allowWhere: '$text',
* allowAttributesOf: '$text',
* isInline: true,
* isObject: true
* } );
*
* schema.register( '$text', {
* allowIn: '$block',
* isInline: true
* isInline: true,
* isContent: true
* } );
*
* They reflect typical editor content that is contained within one root, consists of several blocks
Expand Down Expand Up @@ -1180,14 +1200,18 @@ mix( Schema, ObservableMixin );
* isBlock: true
* } );
*
* The previous rule can be written in a shorter form using inheritance:
*
* schema.register( 'paragraph', {
* inheritAllFrom: '$block'
* } );
*
* Make `imageBlock` a block object, which is allowed everywhere where `$block` is.
* Also, allow `src` and `alt` attributes in it:
*
* schema.register( 'imageBlock', {
* allowWhere: '$block',
* inheritAllFrom: '$blockObject',
* allowAttributes: [ 'src', 'alt' ],
* isBlock: true,
* isObject: true
* } );
*
* Make `caption` allowed in `imageBlock` and make it allow all the content of `$block`s (usually, `$text`).
Expand Down
27 changes: 24 additions & 3 deletions packages/ckeditor5-engine/tests/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,39 @@ describe( 'Model', () => {
expect( schema.isRegistered( '$block' ) ).to.be.true;
expect( schema.isBlock( '$block' ) ).to.be.true;
expect( schema.checkChild( [ '$root' ], '$block' ) ).to.be.true;
expect( schema.checkChild( [ '$container' ], '$block' ) ).to.be.true;
} );

it( 'registers $blockObject to the schema', () => {
expect( schema.isRegistered( '$blockObject' ) ).to.be.true;
expect( schema.isBlock( '$blockObject' ) ).to.be.true;
expect( schema.isObject( '$blockObject' ) ).to.be.true;
expect( schema.checkChild( [ '$root' ], '$blockObject' ) ).to.be.true;
expect( schema.checkChild( [ '$container' ], '$blockObject' ) ).to.be.true;
expect( schema.checkChild( [ '$block' ], '$blockObject' ) ).to.be.false;
} );

it( '$block shares the attributes of $container', () => {
schema.extend( '$container', { allowAttributes: 'foo' } );
it( 'registers $inlineObject to the schema', () => {
expect( schema.isRegistered( '$inlineObject' ) ).to.be.true;
expect( schema.isInline( '$inlineObject' ) ).to.be.true;
expect( schema.isObject( '$inlineObject' ) ).to.be.true;
expect( schema.checkChild( [ '$root' ], '$inlineObject' ) ).to.be.false;
expect( schema.checkChild( [ '$container' ], '$inlineObject' ) ).to.be.false;
expect( schema.checkChild( [ '$block' ], '$inlineObject' ) ).to.be.true;

schema.extend( '$text', {
allowAttributes: [ 'foo', 'bar' ]
} );

expect( schema.checkAttribute( '$block', 'foo' ) ).to.be.true;
expect( schema.checkAttribute( '$inlineObject', 'foo' ) ).to.be.true;
expect( schema.checkAttribute( '$inlineObject', 'bar' ) ).to.be.true;
} );

it( 'registers $text to the schema', () => {
expect( schema.isRegistered( '$text' ) ).to.be.true;
expect( schema.isContent( '$text' ) ).to.be.true;
expect( schema.checkChild( [ '$block' ], '$text' ) ).to.be.true;
expect( schema.checkChild( [ '$container' ], '$text' ) ).to.be.false;
} );

it( 'registers $clipboardHolder to the schema', () => {
Expand Down
Loading