-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7652 from ckeditor/i/7336
Feature (engine): Introduced new upcast `ConversionApi` helper methods - `conversionApi.safeInsert()` and `conversionApi.updateConversionResult()`. New methods are intended to simplify writing event based element-to-element converters. Closes #7336. MAJOR BREAKING CHANGE (engine): The `config.view` parameter for upcast element-to-element conversion helpers configurations is again mandatory. You can retain previous "catch-all" behavior for upcast converter using `config.view = /[\s\S]+/`.
- Loading branch information
Showing
20 changed files
with
1,047 additions
and
279 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
14 changes: 14 additions & 0 deletions
14
packages/ckeditor5-engine/docs/_snippets/framework/build-custom-element-converter-source.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/* globals window */ | ||
|
||
import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor'; | ||
|
||
import { toWidget, toWidgetEditable } from '@ckeditor/ckeditor5-widget/src/utils'; | ||
|
||
window.ClassicEditor = ClassicEditor; | ||
window.toWidget = toWidget; | ||
window.toWidgetEditable = toWidgetEditable; |
43 changes: 43 additions & 0 deletions
43
...ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<style> | ||
.info-box { | ||
border: 1px solid hsl(0, 0%, 80%); | ||
padding: 1em; | ||
background: hsl(0, 0%, 45%); | ||
} | ||
|
||
.info-box-warning { | ||
background: hsl(64, 74%, 85%); | ||
} | ||
|
||
.info-box-info { | ||
background: hsl(205, 100%, 90%); | ||
} | ||
|
||
.info-box-title { | ||
margin-bottom: 1em; | ||
font-weight: bold; | ||
color: inherit; | ||
} | ||
|
||
.info-box-content { | ||
padding: 0 1em; | ||
background: hsl(0, 0%, 100%); | ||
} | ||
</style> | ||
|
||
<div id="editor-custom-element-converter"> | ||
<p>Info:</p> | ||
<div class="info-box info-box-info"> | ||
<div class="info-box-title">Info</div> | ||
<div class="info-box-content"> | ||
<p>Editable content of the <strong>info box</strong>.</p> | ||
</div> | ||
</div> | ||
<p>Warning:</p> | ||
<div class="info-box info-box-warning"> | ||
<div class="info-box-title">Warning</div> | ||
<div class="info-box-content"> | ||
<p>Editable content of the <strong>info box</strong>.</p> | ||
</div> | ||
</div> | ||
</div> |
165 changes: 165 additions & 0 deletions
165
...s/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/** | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
*/ | ||
|
||
/* globals ClassicEditor, toWidget, toWidgetEditable, console, window, document */ | ||
|
||
import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; | ||
|
||
class InfoBox { | ||
constructor( editor ) { | ||
// Schema definition | ||
editor.model.schema.register( 'infoBox', { | ||
allowWhere: '$block', | ||
allowContentOf: '$root', | ||
isObject: true, | ||
allowAttributes: [ 'infoBoxType' ] | ||
} ); | ||
|
||
// Upcast converter. | ||
editor.conversion.for( 'upcast' ) | ||
.add( dispatcher => dispatcher.on( 'element:div', upcastConverter ) ); | ||
|
||
// The downcast conversion must be split as we need a widget in the editing pipeline. | ||
editor.conversion.for( 'editingDowncast' ) | ||
.add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) ); | ||
editor.conversion.for( 'dataDowncast' ) | ||
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) ); | ||
} | ||
} | ||
|
||
function upcastConverter( event, data, conversionApi ) { | ||
const viewInfoBox = data.viewItem; | ||
|
||
// Detect that view element is an info-box div. | ||
// Otherwise, it should be handled by another converter. | ||
if ( !viewInfoBox.hasClass( 'info-box' ) ) { | ||
return; | ||
} | ||
|
||
// Create a model structure. | ||
const modelElement = conversionApi.writer.createElement( 'infoBox', { | ||
infoBoxType: getTypeFromViewElement( viewInfoBox ) | ||
} ); | ||
|
||
// Try to safely insert element - if it returns false the element can't be safely inserted | ||
// into the content, and the conversion process must stop. | ||
if ( !conversionApi.safeInsert( modelElement, data.modelCursor ) ) { | ||
return; | ||
} | ||
|
||
// Mark info-box div as handled by this converter. | ||
conversionApi.consumable.consume( viewInfoBox, { name: true } ); | ||
|
||
// Let's assume that the HTML structure is always the same. | ||
const viewInfoBoxTitle = viewInfoBox.getChild( 0 ); | ||
const viewInfoBoxContent = viewInfoBox.getChild( 1 ); | ||
|
||
// Mark info-box inner elements as handled by this converter. | ||
conversionApi.consumable.consume( viewInfoBoxTitle, { name: true } ); | ||
conversionApi.consumable.consume( viewInfoBoxContent, { name: true } ); | ||
|
||
// Let the editor handle children of the info-box content conversion. | ||
conversionApi.convertChildren( viewInfoBoxContent, modelElement ); | ||
|
||
// Conversion requires updating result data structure properly. | ||
conversionApi.updateConversionResult( modelElement, data ); | ||
} | ||
|
||
function editingDowncastConverter( event, data, conversionApi ) { | ||
let { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi ); | ||
|
||
// Decorate view items as widgets. | ||
infoBox = toWidget( infoBox, conversionApi.writer, { label: 'simple box widget' } ); | ||
infoBoxContent = toWidgetEditable( infoBoxContent, conversionApi.writer ); | ||
|
||
insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ); | ||
} | ||
|
||
function dataDowncastConverter( event, data, conversionApi ) { | ||
const { infoBox, infoBoxContent, infoBoxTitle } = createViewElements( data, conversionApi ); | ||
|
||
insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ); | ||
} | ||
|
||
function createViewElements( data, conversionApi ) { | ||
const type = data.item.getAttribute( 'infoBoxType' ); | ||
|
||
const infoBox = conversionApi.writer.createContainerElement( 'div', { | ||
class: `info-box info-box-${ type.toLowerCase() }` | ||
} ); | ||
const infoBoxContent = conversionApi.writer.createEditableElement( 'div', { | ||
class: 'info-box-content' | ||
} ); | ||
|
||
const infoBoxTitle = conversionApi.writer.createUIElement( 'div', | ||
{ class: 'info-box-title' }, | ||
function( domDocument ) { | ||
const domElement = this.toDomElement( domDocument ); | ||
|
||
domElement.innerText = type; | ||
|
||
return domElement; | ||
} ); | ||
|
||
return { infoBox, infoBoxContent, infoBoxTitle }; | ||
} | ||
|
||
function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBoxContent ) { | ||
conversionApi.consumable.consume( data.item, 'insert' ); | ||
|
||
conversionApi.writer.insert( | ||
conversionApi.writer.createPositionAt( infoBox, 0 ), | ||
infoBoxTitle | ||
); | ||
conversionApi.writer.insert( | ||
conversionApi.writer.createPositionAt( infoBox, 1 ), | ||
infoBoxContent | ||
); | ||
|
||
conversionApi.mapper.bindElements( data.item, infoBox ); | ||
conversionApi.mapper.bindElements( data.item, infoBoxContent ); | ||
|
||
conversionApi.writer.insert( | ||
conversionApi.mapper.toViewPosition( data.range.start ), | ||
infoBox | ||
); | ||
} | ||
|
||
ClassicEditor | ||
.create( document.querySelector( '#editor-custom-element-converter' ), { | ||
cloudServices: CS_CONFIG, | ||
extraPlugins: [ InfoBox ], | ||
image: { | ||
toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ] | ||
}, | ||
table: { | ||
contentToolbar: [ | ||
'tableColumn', | ||
'tableRow', | ||
'mergeTableCells' | ||
] | ||
}, | ||
toolbar: { | ||
viewportTopOffset: window.getViewportTopOffsetConfig() | ||
} | ||
} ) | ||
.then( editor => { | ||
window.editor = editor; | ||
} ) | ||
.catch( err => { | ||
console.error( err.stack ); | ||
} ); | ||
|
||
function getTypeFromViewElement( viewElement ) { | ||
if ( viewElement.hasClass( 'info-box-info' ) ) { | ||
return 'Info'; | ||
} | ||
|
||
if ( viewElement.hasClass( 'info-box-warning' ) ) { | ||
return 'Warning'; | ||
} | ||
|
||
return 'None'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.