From a563c47c64243fab5ad5780a9dcca7467b8b1730 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 12 Sep 2019 13:17:07 +0200 Subject: [PATCH 01/12] Initial implementation. --- lang/contexts.json | 3 ++ src/pagebreak.js | 33 ++++++++++++ src/pagebreakcommand.js | 98 +++++++++++++++++++++++++++++++++++ src/pagebreakediting.js | 104 ++++++++++++++++++++++++++++++++++++++ src/pagebreakui.js | 44 ++++++++++++++++ theme/icons/pagebreak.svg | 1 + theme/pagebreak.css | 14 +++++ 7 files changed, 297 insertions(+) create mode 100644 lang/contexts.json create mode 100644 src/pagebreak.js create mode 100644 src/pagebreakcommand.js create mode 100644 src/pagebreakediting.js create mode 100644 src/pagebreakui.js create mode 100644 theme/icons/pagebreak.svg create mode 100644 theme/pagebreak.css diff --git a/lang/contexts.json b/lang/contexts.json new file mode 100644 index 0000000..ef474ae --- /dev/null +++ b/lang/contexts.json @@ -0,0 +1,3 @@ +{ + "Page break": "Page break" +} diff --git a/src/pagebreak.js b/src/pagebreak.js new file mode 100644 index 0000000..6b7b59c --- /dev/null +++ b/src/pagebreak.js @@ -0,0 +1,33 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module page-break/pagebreak + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import PageBreakEditing from './pagebreakediting'; +import PageBreakUI from './pagebreakui'; + +/** + * The page break plugin provides a possibility to insert a page break in the rich-text editor. + * + * @extends module:core/plugin~Plugin + */ +export default class PageBreak extends Plugin { + /** + * @inheritDoc + */ + static get requires() { + return [ PageBreakEditing, PageBreakUI ]; + } + + /** + * @inheritDoc + */ + static get pluginName() { + return 'PageBreak'; + } +} diff --git a/src/pagebreakcommand.js b/src/pagebreakcommand.js new file mode 100644 index 0000000..ce5edfe --- /dev/null +++ b/src/pagebreakcommand.js @@ -0,0 +1,98 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module page-break/pagebreakcommand + */ + +import Command from '@ckeditor/ckeditor5-core/src/command'; +import { findOptimalInsertionPosition } from '@ckeditor/ckeditor5-widget/src/utils'; + +/** + * The insert a page break command. + * + * The command is registered by the {@link module:page-break/pagebreakediting~PageBreakEditing} as `'pageBreak'`. + * + * To insert the page break at the current selection, execute the command: + * + * editor.execute( 'pageBreak' ); + * + * @extends module:core/command~Command + */ +export default class PageBreakCommand extends Command { + /** + * @inheritDoc + */ + refresh() { + this.isEnabled = isPageBreakAllowed( this.editor.model ); + } + + /** + * Executes the command. + * + * @fires execute + */ + execute() { + const model = this.editor.model; + + model.change( writer => { + const modelElement = writer.createElement( 'pageBreak' ); + + model.insertContent( modelElement ); + } ); + } +} + +// Checks if the `pageBreak` element can be inserted at current model selection. +// +// @param {module:engine/model/model~Model} model +// @returns {Boolean} +function isPageBreakAllowed( model ) { + const schema = model.schema; + const selection = model.document.selection; + + return isPageBreakAllowedInParent( selection, schema, model ) && + !checkSelectionOnObject( selection, schema ); +} + +// Checks if page break is allowed by schema in optimal insertion parent. +// +// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection +// @param {module:engine/model/schema~Schema} schema +// @param {module:engine/model/model~Model} model Model instance. +// @returns {Boolean} +function isPageBreakAllowedInParent( selection, schema, model ) { + const parent = getInsertPageBreakParent( selection, model ); + + return schema.checkChild( parent, 'pageBreak' ); +} + +// Check if selection is on object. +// +// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection +// @param {module:engine/model/schema~Schema} schema +// @returns {Boolean} +function checkSelectionOnObject( selection, schema ) { + const selectedElement = selection.getSelectedElement(); + + return selectedElement && schema.isObject( selectedElement ); +} + +// Returns a node that will be used to insert page break with `model.insertContent` to check if page break can be placed there. +// +// @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection +// @param {module:engine/model/model~Model} model Model instance. +// @returns {module:engine/model/element~Element} +function getInsertPageBreakParent( selection, model ) { + const insertAt = findOptimalInsertionPosition( selection, model ); + + const parent = insertAt.parent; + + if ( parent.isEmpty && !parent.is( '$root' ) ) { + return parent.parent; + } + + return parent; +} diff --git a/src/pagebreakediting.js b/src/pagebreakediting.js new file mode 100644 index 0000000..dde94b6 --- /dev/null +++ b/src/pagebreakediting.js @@ -0,0 +1,104 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module page-break/pagebreakediting + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import PageBreakCommand from './pagebreakcommand'; +import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils'; +import first from '@ckeditor/ckeditor5-utils/src/first'; + +import '../theme/pagebreak.css'; + +/** + * The page break editing feature. + * + * @extends module:core/plugin~Plugin + */ +export default class PageBreakEditing extends Plugin { + /** + * @inheritDoc + */ + init() { + const editor = this.editor; + const schema = editor.model.schema; + const t = editor.t; + const conversion = editor.conversion; + + schema.register( 'pageBreak', { + isObject: true, + allowWhere: '$block' + } ); + + conversion.for( 'dataDowncast' ).elementToElement( { + model: 'pageBreak', + view: ( modelElement, viewWriter ) => { + const divElement = viewWriter.createContainerElement( 'div', { + style: 'page-break-after: always' + } ); + + const spanElement = viewWriter.createContainerElement( 'span', { + style: 'display: none' + } ); + + viewWriter.insert( viewWriter.createPositionAt( divElement, 0 ), spanElement ); + + return divElement; + } + } ); + + conversion.for( 'editingDowncast' ).elementToElement( { + model: 'pageBreak', + view: ( modelElement, viewWriter ) => { + const label = t( 'Page break' ); + const viewWrapper = viewWriter.createContainerElement( 'div' ); + + viewWriter.addClass( 'ck-page-break', viewWrapper ); + viewWriter.setCustomProperty( 'pageBreak', true, viewWrapper ); + + return toPageBreakWidget( viewWrapper, viewWriter, label ); + } + } ); + + conversion.for( 'upcast' ) + .elementToElement( { + view: element => { + // The "page break" div must have specified value for the 'page-break-after' definition and single child only. + if ( !element.is( 'div' ) || element.getStyle( 'page-break-after' ) != 'always' || element.childCount != 1 ) { + return; + } + + const viewSpan = first( element.getChildren() ); + + // The child must be the "span" element that is not displayed and doesn't have any child. + if ( !viewSpan.is( 'span' ) || viewSpan.getStyle( 'display' ) != 'none' || viewSpan.childCount != 0 ) { + return; + } + + return { name: true }; + }, + model: 'pageBreak' + } ); + + editor.commands.add( 'pageBreak', new PageBreakCommand( editor ) ); + } +} + +// Converts a given {@link module:engine/view/element~Element} to a page break widget: +// * Adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to +// recognize the page break widget element. +// * Calls the {@link module:widget/utils~toWidget} function with the proper element's label creator. +// +// @param {module:engine/view/element~Element} viewElement +// @param {module:engine/view/downcastwriter~DowncastWriter} writer An instance of the view writer. +// @param {String} label The element's label. +// @returns {module:engine/view/element~Element} +function toPageBreakWidget( viewElement, writer, label ) { + writer.setCustomProperty( 'pageBreak', true, viewElement ); + + return toWidget( viewElement, writer, { label } ); +} diff --git a/src/pagebreakui.js b/src/pagebreakui.js new file mode 100644 index 0000000..5566b4e --- /dev/null +++ b/src/pagebreakui.js @@ -0,0 +1,44 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module page-break/pagebreakui + */ + +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import pageBreakIcon from '../theme/icons/pagebreak.svg'; + +/** + * The page break UI plugin. + * + * @extends module:core/plugin~Plugin + */ +export default class PageBreakUI extends Plugin { + init() { + const editor = this.editor; + const t = editor.t; + + // Add pageBreak button to feature components. + editor.ui.componentFactory.add( 'pageBreak', locale => { + const command = editor.commands.get( 'pageBreak' ); + const view = new ButtonView( locale ); + + view.set( { + label: t( 'Page break' ), + icon: pageBreakIcon, + tooltip: true, + isToggleable: true + } ); + + view.bind( 'isEnabled' ).to( command, 'isEnabled' ); + + // Execute command. + this.listenTo( view, 'execute', () => editor.execute( 'pageBreak' ) ); + + return view; + } ); + } +} diff --git a/theme/icons/pagebreak.svg b/theme/icons/pagebreak.svg new file mode 100644 index 0000000..e67e9ca --- /dev/null +++ b/theme/icons/pagebreak.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/pagebreak.css b/theme/pagebreak.css new file mode 100644 index 0000000..a6096db --- /dev/null +++ b/theme/pagebreak.css @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +.ck .ck-page-break { + width: calc( 100% - 1.2em ); + padding: 6px 0; + margin-left: auto; + margin-right: auto; + border: 2px dashed var(--ck-color-toolbar-border); + border-left: 0; + border-right: 0; +} From 1a970eb9815e6333d4a457497b0c23df81cfcc78 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 12 Sep 2019 13:17:14 +0200 Subject: [PATCH 02/12] Added tests. --- tests/manual/pagebreak.html | 20 ++++ tests/manual/pagebreak.js | 64 +++++++++++ tests/manual/pagebreak.md | 1 + tests/pagebreak.js | 18 +++ tests/pagebreakcommand.js | 154 ++++++++++++++++++++++++++ tests/pagebreakediting.js | 215 ++++++++++++++++++++++++++++++++++++ tests/pagebreakui.js | 67 +++++++++++ 7 files changed, 539 insertions(+) create mode 100644 tests/manual/pagebreak.html create mode 100644 tests/manual/pagebreak.js create mode 100644 tests/manual/pagebreak.md create mode 100644 tests/pagebreak.js create mode 100644 tests/pagebreakcommand.js create mode 100644 tests/pagebreakediting.js create mode 100644 tests/pagebreakui.js diff --git a/tests/manual/pagebreak.html b/tests/manual/pagebreak.html new file mode 100644 index 0000000..821459b --- /dev/null +++ b/tests/manual/pagebreak.html @@ -0,0 +1,20 @@ + + +
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris viverra ipsum a sapien accumsan, in fringilla ligula congue. Suspendisse eget urna ac nulla dignissim sollicitudin vel non sem. Curabitur consequat nisi vel orci mollis tincidunt. Nam eget sapien non ligula aliquet commodo vel sed lectus. Sed arcu orci, vehicula vitae augue lobortis, posuere tristique nisl. Sed eleifend venenatis magna in elementum. Cras sit amet arcu mi. Suspendisse vel purus a ex maximus pharetra quis in massa. Mauris pellentesque leo sed mi faucibus molestie. Cras felis justo, volutpat sed erat at, lacinia fermentum nunc. Pellentesque est leo, dignissim at odio sit amet, vulputate placerat turpis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla eget pharetra enim. Donec fermentum ligula est, quis ultrices arcu tristique eu. Vivamus a dui sem.

+
 
+

Nunc mattis vehicula quam, eu ultrices elit. Nam rutrum, magna vel pharetra fermentum, mauris lectus aliquam mauris, ultrices sagittis massa justo ut urna. Curabitur nec odio commodo, euismod orci quis, lacinia magna. Cras cursus id dui et eleifend. Fusce eget consequat ante. Quisque at odio diam. Praesent vel lacinia urna, vel hendrerit diam. Nulla facilisi. Curabitur finibus augue luctus mi dapibus rutrum. Vestibulum id mi quam. Donec accumsan felis lacus, ac luctus arcu sagittis eget. Suspendisse porttitor mattis magna, in finibus ligula suscipit non. Mauris dictum convallis vehicula.

+
+
+ + diff --git a/tests/manual/pagebreak.js b/tests/manual/pagebreak.js new file mode 100644 index 0000000..6a6e405 --- /dev/null +++ b/tests/manual/pagebreak.js @@ -0,0 +1,64 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals console, window, document */ + +import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'; +import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset'; +import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload'; +import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage'; +import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; +import PageBreak from '../../src/pagebreak'; + +ClassicEditor + .create( document.querySelector( '#editor' ), { + cloudServices: CS_CONFIG, + plugins: [ ArticlePluginSet, ImageUpload, EasyImage, PageBreak ], + toolbar: [ + 'heading', + '|', + 'bold', 'italic', 'numberedList', 'bulletedList', + '|', + 'link', 'blockquote', 'imageUpload', 'insertTable', 'mediaEmbed', + '|', + 'undo', 'redo', + '|', + 'pageBreak' + ], + image: { + styles: [ + 'full', + 'alignLeft', + 'alignRight' + ], + toolbar: [ + 'imageStyle:alignLeft', + 'imageStyle:full', + 'imageStyle:alignRight', + '|', + 'imageTextAlternative' + ] + }, + table: { + contentToolbar: [ + 'tableColumn', + 'tableRow', + 'mergeTableCells' + ] + }, + } ) + .then( editor => { + window.editor = editor; + + const logDataButton = document.querySelector( '#log-data' ); + + logDataButton.disabled = false; + logDataButton.addEventListener( 'click', () => { + console.log( window.editor.getData() ); + } ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/tests/manual/pagebreak.md b/tests/manual/pagebreak.md new file mode 100644 index 0000000..fe65148 --- /dev/null +++ b/tests/manual/pagebreak.md @@ -0,0 +1 @@ +There should be a page break widget between two paragraphs in the editor. diff --git a/tests/pagebreak.js b/tests/pagebreak.js new file mode 100644 index 0000000..ce991f6 --- /dev/null +++ b/tests/pagebreak.js @@ -0,0 +1,18 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import PageBreak from '../src/pagebreak'; +import PageBreakEditing from '../src/pagebreakediting'; +import PageBreakUI from '../src/pagebreakui'; + +describe( 'PageBreak', () => { + it( 'should require PageBreakEditing and PageBreakUI', () => { + expect( PageBreak.requires ).to.deep.equal( [ PageBreakEditing, PageBreakUI ] ); + } ); + + it( 'should be named', () => { + expect( PageBreak.pluginName ).to.equal( 'PageBreak' ); + } ); +} ); diff --git a/tests/pagebreakcommand.js b/tests/pagebreakcommand.js new file mode 100644 index 0000000..af20d86 --- /dev/null +++ b/tests/pagebreakcommand.js @@ -0,0 +1,154 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import PageBreakEditing from '../src/pagebreakediting'; + +describe( 'PageBreakCommand', () => { + let editor, model, editorElement, command; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); + + return ClassicTestEditor + .create( editorElement, { + plugins: [ Paragraph, PageBreakEditing ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + command = editor.commands.get( 'pageBreak' ); + } ); + } ); + + afterEach( () => { + return editor.destroy() + .then( () => { + editorElement.remove(); + } ); + } ); + + describe( 'isEnabled', () => { + it( 'should be true when the selection directly in the root', () => { + model.enqueueChange( 'transparent', () => { + setModelData( model, '[]' ); + + command.refresh(); + expect( command.isEnabled ).to.be.true; + } ); + } ); + + it( 'should be true when the selection is in empty block', () => { + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be true when the selection directly in a paragraph', () => { + setModelData( model, 'foo[]' ); + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be true when the selection directly in a block', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( '$text', { allowIn: 'block' } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } ); + + setModelData( model, 'foo[]' ); + expect( command.isEnabled ).to.be.true; + } ); + + it( 'should be false when the selection is on other page break element', () => { + setModelData( model, '[]' ); + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be false when the selection is on other object', () => { + model.schema.register( 'object', { isObject: true, allowIn: '$root' } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'object', view: 'object' } ); + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.false; + } ); + + it( 'should be true when the selection is inside block element inside isLimit element which allows page break', () => { + model.schema.register( 'table', { allowWhere: '$block', isLimit: true, isObject: true, isBlock: true } ); + model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } ); + model.schema.register( 'tableCell', { allowIn: 'tableRow', isLimit: true } ); + model.schema.extend( '$block', { allowIn: 'tableCell' } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'table', view: 'table' } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableRow', view: 'tableRow' } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableCell', view: 'tableCell' } ); + + setModelData( model, 'foo[]
' ); + } ); + + it( 'should be false when schema disallows page break', () => { + model.schema.register( 'block', { inheritAllFrom: '$block' } ); + model.schema.extend( 'paragraph', { allowIn: 'block' } ); + // Block page break in block. + model.schema.addChildCheck( ( context, childDefinition ) => { + if ( childDefinition.name === 'pageBreak' && context.last.name === 'block' ) { + return false; + } + } ); + editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } ); + + setModelData( model, '[]' ); + + expect( command.isEnabled ).to.be.false; + } ); + } ); + + describe( 'execute()', () => { + it( 'should create a single batch', () => { + setModelData( model, 'foo[]' ); + + const spy = sinon.spy(); + + model.document.on( 'change', spy ); + + command.execute(); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'should insert a page break in an empty root and select it', () => { + setModelData( model, '[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( '[]' ); + } ); + + it( 'should split an element where selection is placed and insert a page break (non-collapsed selection)', () => { + setModelData( model, 'f[o]o' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'f[]o' + ); + } ); + + it( 'should split an element where selection is placed and insert a page break (collapsed selection)', () => { + setModelData( model, 'fo[]o' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'fo[]o' + ); + } ); + } ); +} ); diff --git a/tests/pagebreakediting.js b/tests/pagebreakediting.js new file mode 100644 index 0000000..1e446c0 --- /dev/null +++ b/tests/pagebreakediting.js @@ -0,0 +1,215 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import PageBreakEditing from '../src/pagebreakediting'; +import PageBreakCommand from '../src/pagebreakcommand'; +import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; +import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; +import { isWidget } from '@ckeditor/ckeditor5-widget/src/utils'; +import env from '@ckeditor/ckeditor5-utils/src/env'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +describe( 'PageBreakEditing', () => { + let editor, model, view, viewDocument; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + // Most tests assume non-edge environment but we do not set `contenteditable=false` on Edge so stub `env.isEdge`. + testUtils.sinon.stub( env, 'isEdge' ).get( () => false ); + + return VirtualTestEditor + .create( { + plugins: [ PageBreakEditing ] + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + view = editor.editing.view; + viewDocument = view.document; + } ); + } ); + + it( 'should be loaded', () => { + expect( editor.plugins.get( PageBreakEditing ) ).to.be.instanceOf( PageBreakEditing ); + } ); + + it( 'should set proper schema rules', () => { + expect( model.schema.checkChild( [ '$root' ], 'pageBreak' ) ).to.be.true; + + expect( model.schema.isObject( 'pageBreak' ) ).to.be.true; + + expect( model.schema.checkChild( [ '$root', 'pageBreak' ], '$text' ) ).to.be.false; + expect( model.schema.checkChild( [ '$root', '$block' ], 'pageBreak' ) ).to.be.false; + } ); + + it( 'should register imageInsert command', () => { + expect( editor.commands.get( 'pageBreak' ) ).to.be.instanceOf( PageBreakCommand ); + } ); + + describe( 'conversion in data pipeline', () => { + describe( 'model to view', () => { + it( 'should convert', () => { + setModelData( model, '' ); + + expect( editor.getData() ).to.equal( + '
 
' + ); + } ); + } ); + + describe( 'view to model', () => { + it( 'should convert the page break code element', () => { + editor.setData( '
 
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert in wrong context', () => { + model.schema.register( 'div', { inheritAllFrom: '$block' } ); + model.schema.addChildCheck( ( ctx, childDef ) => { + if ( ctx.endsWith( '$root' ) && childDef.name == 'pageBreak' ) { + return false; + } + } ); + + editor.conversion.elementToElement( { model: 'div', view: 'div' } ); + + editor.setData( '
 
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '
' ); + } ); + + it( 'should not convert if outer div has wrong styles', () => { + editor.setData( '
 
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if outer div has no children', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if outer div has too many children', () => { + editor.setData( + '
' + + ' ' + + ' ' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if inner span has wrong styles', () => { + editor.setData( + '
' + + ' ' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if inner span has wrong styles', () => { + editor.setData( + '
' + + ' ' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if inner span has any children', () => { + editor.setData( + '
' + + '' + + 'Foo' + + '' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if inner span has text', () => { + editor.setData( + '
' + + 'Foo' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert inner span if outer div was already consumed', () => { + model.schema.register( 'section', { inheritAllFrom: '$block' } ); + editor.conversion.elementToElement( { model: 'section', view: 'section' } ); + + model.schema.register( 'span', { inheritAllFrom: '$block' } ); + editor.model.schema.extend( '$text', { allowAttributes: 'foo' } ); + + editor.conversion.attributeToElement( { + model: 'foo', + view: { + name: 'span', + styles: { + display: 'none' + } + } + } ); + + editor.setData( + '
' + + '
' + + ' ' + + '
' + + 'Foo' + + '
' + ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '
<$text foo="true">Foo
' ); + } ); + } ); + } ); + + describe( 'conversion in editing pipeline', () => { + describe( 'model to view', () => { + it( 'should convert', () => { + setModelData( model, '' ); + + expect( getViewData( view, { withoutSelection: true } ) ).to.equal( + '
' + ); + } ); + + it( 'converted element should be widgetized', () => { + setModelData( model, '' ); + const widget = viewDocument.getRoot().getChild( 0 ); + + expect( widget.name ).to.equal( 'div' ); + expect( isPageBreakWidget( widget ) ).to.be.true; + } ); + } ); + } ); + + function isPageBreakWidget( viewElement ) { + return !!viewElement.getCustomProperty( 'pageBreak' ) && isWidget( viewElement ); + } +} ); diff --git a/tests/pagebreakui.js b/tests/pagebreakui.js new file mode 100644 index 0000000..8d82224 --- /dev/null +++ b/tests/pagebreakui.js @@ -0,0 +1,67 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals document */ + +import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import PageBreakEditing from '../src/pagebreakediting'; +import PageBreakUI from '../src/pagebreakui'; +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; + +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; + +describe( 'PageBreakUI', () => { + let editor, editorElement, pageBreakView; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + editorElement = document.createElement( 'div' ); + document.body.appendChild( editorElement ); + + return ClassicTestEditor + .create( editorElement, { + plugins: [ Paragraph, PageBreakEditing, PageBreakUI ] + } ) + .then( newEditor => { + editor = newEditor; + + pageBreakView = editor.ui.componentFactory.create( 'pageBreak' ); + } ); + } ); + + afterEach( () => { + return editor.destroy() + .then( () => { + editorElement.remove(); + } ); + } ); + + it( 'should register pageBreak feature component', () => { + expect( pageBreakView ).to.be.instanceOf( ButtonView ); + expect( pageBreakView.label ).to.equal( 'Page break' ); + expect( pageBreakView.icon ).to.match( / { + const executeSpy = testUtils.sinon.spy( editor, 'execute' ); + + pageBreakView.fire( 'execute' ); + + sinon.assert.calledOnce( executeSpy ); + sinon.assert.calledWithExactly( executeSpy, 'pageBreak' ); + } ); + + it( 'should bind model to pageBreak command', () => { + const command = editor.commands.get( 'pageBreak' ); + + expect( pageBreakView.isEnabled ).to.be.true; + + command.isEnabled = false; + expect( pageBreakView.isEnabled ).to.be.false; + } ); +} ); From ab335c4e12ccf3d652b352cd9fbb4ac1e83afbab Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 12 Sep 2019 13:17:37 +0200 Subject: [PATCH 03/12] Added docs. --- docs/_snippets/features/page-break.html | 8 +++ docs/_snippets/features/page-break.js | 66 +++++++++++++++++++++++++ docs/api/page-break.md | 34 +++++++++++++ docs/features/page-break.md | 55 +++++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 docs/_snippets/features/page-break.html create mode 100644 docs/_snippets/features/page-break.js create mode 100644 docs/api/page-break.md create mode 100644 docs/features/page-break.md diff --git a/docs/_snippets/features/page-break.html b/docs/_snippets/features/page-break.html new file mode 100644 index 0000000..3ec1697 --- /dev/null +++ b/docs/_snippets/features/page-break.html @@ -0,0 +1,8 @@ +
+

What is Lorem Ipsum?

+

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry’s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.

+
 
+

It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

+
+ + diff --git a/docs/_snippets/features/page-break.js b/docs/_snippets/features/page-break.js new file mode 100644 index 0000000..729adc6 --- /dev/null +++ b/docs/_snippets/features/page-break.js @@ -0,0 +1,66 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/* globals window, document, console */ + +import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor'; +import PageBreak from '@ckeditor/ckeditor5-page-break/src/pagebreak'; +import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config'; + +ClassicEditor.builtinPlugins.push( PageBreak ); + +ClassicEditor + .create( document.querySelector( '#snippet-page-break' ), { + toolbar: { + items: [ + 'heading', + '|', + 'bold', + 'italic', + 'bulletedList', + 'numberedList', + 'blockQuote', + 'link', + '|', + 'imageUpload', + 'mediaEmbed', + 'insertTable', + 'pageBreak', + '|', + 'undo', + 'redo', + ], + viewportTopOffset: window.getViewportTopOffsetConfig() + }, + image: { + styles: [ + 'full', + 'alignLeft', + 'alignRight' + ], + toolbar: [ + 'imageStyle:alignLeft', + 'imageStyle:full', + 'imageStyle:alignRight', + '|', + 'imageTextAlternative' + ] + }, + table: { + contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ] + }, + cloudServices: CS_CONFIG + } ) + .then( editor => { + window.editor = editor; + + // The "Log editor data" button logic. + document.querySelector( '#log-data' ).addEventListener( 'click', () => { + console.log( editor.getData() ); + } ); + } ) + .catch( err => { + console.error( err.stack ); + } ); diff --git a/docs/api/page-break.md b/docs/api/page-break.md new file mode 100644 index 0000000..dfcbff2 --- /dev/null +++ b/docs/api/page-break.md @@ -0,0 +1,34 @@ +--- +category: api-reference +--- + +# Page break feature for CKEditor 5 + +[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-page-break.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-page-break) + +This package implements the page break feature for CKEditor 5. + +## Demo + +Check out the {@link features/page-break#demo demo in the Page break feature} guide. + +## Documentation + +See the {@link features/page-break Page break feature} guide and the {@link module:page-break/pagebreak~PageBreak} plugin documentation. + +## Installation + +```bash +npm install --save @ckeditor/ckeditor5-page-break +``` + +## Contribute + +The source code of this package is available on GitHub in https://github.com/ckeditor/ckeditor5-page-break. + +## External links + +* [`@ckeditor/ckeditor5-page-break` on npm](https://www.npmjs.com/package/@ckeditor/ckeditor5-page-break) +* [`ckeditor/ckeditor5-page-break` on GitHub](https://github.com/ckeditor/ckeditor5-page-break) +* [Issue tracker](https://github.com/ckeditor/ckeditor5/issues) +* [Changelog](https://github.com/ckeditor/ckeditor5-page-break/blob/master/CHANGELOG.md) diff --git a/docs/features/page-break.md b/docs/features/page-break.md new file mode 100644 index 0000000..b1ee624 --- /dev/null +++ b/docs/features/page-break.md @@ -0,0 +1,55 @@ +--- +category: features +menu-title: Page break +--- + +# Page break + +The {@link module:page-break/pagebreak~PageBreak} plugin provides a possibility to insert a page break in the rich-text editor. + +## Demo + +Use the editor below to see the {@link module:page-break/pagebreak~PageBreak} plugin in action. Open the web browser console and click the button below to see the page-break snippet code in the editor output data. + +{@snippet features/page-break} + +## Installation + +To add this feature to your rich-text editor, install the [`@ckeditor/ckeditor5-page-break`](https://www.npmjs.com/package/@ckeditor/ckeditor5-page-break) package: + +```bash +npm install --save @ckeditor/ckeditor5-page-break +``` + +And add it to your plugin list configuration: + +```js +import PageBreak from '@ckeditor/ckeditor5-page-break/src/pagebreak'; + +ClassicEditor + .create( document.querySelector( '#editor' ), { + plugins: [ PageBreak, ... ], + toolbar: [ 'pageBreak', ... ], + } ) + .then( ... ) + .catch( ... ); +``` + + + Read more about {@link builds/guides/integration/installing-plugins installing plugins}. + + +## Common API + +The {@link module:page-break/pagebreak~PageBreak} plugin registers the UI button component (`'pageBreak'`) and the `'pageBreak'` command implemented by {@link module:page-break/pagebreakcommand~PageBreakCommand}. + +The command can be executed using the {@link module:core/editor/editor~Editor#execute `editor.execute()`} method: + +```js +// Inserts the page break to the selected content. +editor.execute( 'pageBreak' ); +``` + +## Contribute + +The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5-page-break. From b3a2a071383766895208120c4588ae2828d4dee5 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 19 Sep 2019 09:09:08 +0200 Subject: [PATCH 04/12] Improvements for selection after inserting the page break element. --- src/pagebreakcommand.js | 21 +++++- tests/pagebreakcommand.js | 131 +++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 5 deletions(-) diff --git a/src/pagebreakcommand.js b/src/pagebreakcommand.js index ce5edfe..dac4250 100644 --- a/src/pagebreakcommand.js +++ b/src/pagebreakcommand.js @@ -38,9 +38,26 @@ export default class PageBreakCommand extends Command { const model = this.editor.model; model.change( writer => { - const modelElement = writer.createElement( 'pageBreak' ); + const pageBreakElement = writer.createElement( 'pageBreak' ); - model.insertContent( modelElement ); + model.insertContent( pageBreakElement ); + + let nextElement = pageBreakElement.nextSibling; + + // Check whether an element next to the inserted page break is defined and can contain a text. + const canSetSelection = nextElement && model.schema.checkChild( nextElement, '$text' ); + + // If the element is missing, but a paragraph could be inserted next to the page break, let's add it. + if ( !canSetSelection && model.schema.checkChild( pageBreakElement.parent, 'paragraph' ) ) { + nextElement = writer.createElement( 'paragraph' ); + + writer.insert( nextElement, writer.createPositionAfter( pageBreakElement ) ); + } + + // Put the selection inside the element, at the beginning. + if ( nextElement ) { + writer.setSelection( nextElement, 0 ); + } } ); } } diff --git a/tests/pagebreakcommand.js b/tests/pagebreakcommand.js index af20d86..17131d9 100644 --- a/tests/pagebreakcommand.js +++ b/tests/pagebreakcommand.js @@ -111,6 +111,14 @@ describe( 'PageBreakCommand', () => { } ); describe( 'execute()', () => { + beforeEach( () => { + model.schema.register( 'heading1', { inheritAllFrom: '$block' } ); + editor.conversion.elementToElement( { model: 'heading1', view: 'h1' } ); + + model.schema.register( 'media', { allowWhere: '$block' } ); + editor.conversion.elementToElement( { model: 'media', view: 'div' } ); + } ); + it( 'should create a single batch', () => { setModelData( model, 'foo[]' ); @@ -123,7 +131,14 @@ describe( 'PageBreakCommand', () => { sinon.assert.calledOnce( spy ); } ); - it( 'should insert a page break in an empty root and select it', () => { + it( 'should insert a page break in an empty root and select it (a paragraph cannot be inserted)', () => { + // Block a paragraph in $root. + model.schema.addChildCheck( ( context, childDefinition ) => { + if ( childDefinition.name === 'paragraph' && context.last.name === '$root' ) { + return false; + } + } ); + setModelData( model, '[]' ); command.execute(); @@ -137,7 +152,7 @@ describe( 'PageBreakCommand', () => { command.execute(); expect( getModelData( model ) ).to.equal( - 'f[]o' + 'f[]o' ); } ); @@ -147,7 +162,117 @@ describe( 'PageBreakCommand', () => { command.execute(); expect( getModelData( model ) ).to.equal( - 'fo[]o' + 'fo[]o' + ); + } ); + + it( 'should create an empty paragraph after inserting a page break after a paragraph and place selection inside', () => { + setModelData( model, 'foo[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]' + ); + } ); + + it( 'should create an empty paragraph after inserting a page break after a heading and place selection inside', () => { + setModelData( model, 'foo[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]' + ); + } ); + + it( 'should create an empty paragraph after inserting a page break and next element must not having text', () => { + setModelData( model, 'foo[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]' + ); + } ); + + it( 'should create an empty paragraph after inserting a page break in heading and next element must not having text', () => { + setModelData( model, 'foo[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]' + ); + } ); + + it( 'should not create an empty paragraph if a page break split an element with text', () => { + setModelData( model, 'foo[]bar' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]bar' + ); + } ); + + it( 'should replace an empty paragraph with a page break and insert another paragraph next to', () => { + setModelData( model, '[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + '[]' + ); + } ); + + it( 'should replace an empty paragraph with a page break and move the selection to next paragraph', () => { + setModelData( model, 'foo[]bar' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]bar' + ); + } ); + + it( 'should replace an empty paragraph with a page break and move the selection to next element that has text', () => { + setModelData( model, 'foo[]bar' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]bar' + ); + } ); + + it( 'should replace an empty block element with a page break and insert a paragraph next to', () => { + setModelData( model, '[]' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + '[]' + ); + } ); + + it( 'should move the selection to next element if it allows having text (paragraph + heading)', () => { + setModelData( model, 'foo[]bar' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]bar' + ); + } ); + + it( 'should move the selection to next element if it allows having text (heading + paragraph)', () => { + setModelData( model, 'foo[]bar' ); + + command.execute(); + + expect( getModelData( model ) ).to.equal( + 'foo[]bar' ); } ); } ); From bfe150c37d5929fc043a748bfedf65a7b8bd2206 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 19 Sep 2019 09:09:25 +0200 Subject: [PATCH 05/12] Added missing dependencies to pkg.json. --- package.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package.json b/package.json index efa3b7d..1ff7344 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,18 @@ "ckeditor5-plugin" ], "dependencies": { + "@ckeditor/ckeditor5-core": "^12.2.1", + "@ckeditor/ckeditor5-ui": "^14.0.0", + "@ckeditor/ckeditor5-widget": "^11.0.4" }, "devDependencies": { + "@ckeditor/ckeditor5-cloud-services": "^11.0.5", + "@ckeditor/ckeditor5-engine": "^14.0.0", + "@ckeditor/ckeditor5-editor-classic": "^12.1.3", + "@ckeditor/ckeditor5-easy-image": "^11.0.5", + "@ckeditor/ckeditor5-image": "^14.0.0", + "@ckeditor/ckeditor5-paragraph": "^11.0.5", + "@ckeditor/ckeditor5-utils": "^14.0.0", "eslint": "^5.5.0", "eslint-config-ckeditor5": "^2.0.0", "husky": "^1.3.1", From d1f3b694cf672db215e4147fcac01955107f8b0a Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Thu, 19 Sep 2019 11:20:37 +0200 Subject: [PATCH 06/12] Improved the page-break demo. --- docs/_snippets/features/page-break.html | 18 +++++++++++++++++- docs/_snippets/features/page-break.js | 22 +++++++++++++++++++--- docs/features/page-break.md | 2 +- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/docs/_snippets/features/page-break.html b/docs/_snippets/features/page-break.html index 3ec1697..98d2406 100644 --- a/docs/_snippets/features/page-break.html +++ b/docs/_snippets/features/page-break.html @@ -5,4 +5,20 @@

What is Lorem Ipsum?

It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

- + + + + diff --git a/docs/_snippets/features/page-break.js b/docs/_snippets/features/page-break.js index 729adc6..4425fa5 100644 --- a/docs/_snippets/features/page-break.js +++ b/docs/_snippets/features/page-break.js @@ -56,9 +56,25 @@ ClassicEditor .then( editor => { window.editor = editor; - // The "Log editor data" button logic. - document.querySelector( '#log-data' ).addEventListener( 'click', () => { - console.log( editor.getData() ); + // The "Print editor data" button logic. + document.querySelector( '#print-data-action' ).addEventListener( 'click', () => { + const snippetCSSElement = [ ...document.querySelectorAll( 'link' ) ] + .find( linkElement => linkElement.href.endsWith( 'snippet.css' ) ); + + const iframeElement = document.querySelector( '#print-data-container' ); + + iframeElement.srcdoc = '' + + '' + + `${ document.title }` + + `` + + '' + + '' + + editor.getData() + + '' + + '' + + ''; } ); } ) .catch( err => { diff --git a/docs/features/page-break.md b/docs/features/page-break.md index b1ee624..c62e62d 100644 --- a/docs/features/page-break.md +++ b/docs/features/page-break.md @@ -9,7 +9,7 @@ The {@link module:page-break/pagebreak~PageBreak} plugin provides a possibility ## Demo -Use the editor below to see the {@link module:page-break/pagebreak~PageBreak} plugin in action. Open the web browser console and click the button below to see the page-break snippet code in the editor output data. +Use the editor below to see the {@link module:page-break/pagebreak~PageBreak} plugin in action. Click the button below in order to open a print preview window. {@snippet features/page-break} From 7362149f4bbf618b8304323770b9531d0579ec75 Mon Sep 17 00:00:00 2001 From: Damian Konopka Date: Thu, 19 Sep 2019 13:41:50 +0200 Subject: [PATCH 07/12] Added `clear:both` to be unified with print preview. --- theme/pagebreak.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/theme/pagebreak.css b/theme/pagebreak.css index a6096db..adb59aa 100644 --- a/theme/pagebreak.css +++ b/theme/pagebreak.css @@ -4,10 +4,8 @@ */ .ck .ck-page-break { - width: calc( 100% - 1.2em ); - padding: 6px 0; - margin-left: auto; - margin-right: auto; + clear: both; + padding: 5px; border: 2px dashed var(--ck-color-toolbar-border); border-left: 0; border-right: 0; From 82674e8e6d5fd20f791c93a6e4272e1a6c11b6ac Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Mon, 23 Sep 2019 14:38:19 +0200 Subject: [PATCH 08/12] The feature converter is more detailed. --- src/pagebreakediting.js | 10 ++++++++-- tests/pagebreakediting.js | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/pagebreakediting.js b/src/pagebreakediting.js index dde94b6..7bc3ffd 100644 --- a/src/pagebreakediting.js +++ b/src/pagebreakediting.js @@ -74,8 +74,14 @@ export default class PageBreakEditing extends Plugin { const viewSpan = first( element.getChildren() ); - // The child must be the "span" element that is not displayed and doesn't have any child. - if ( !viewSpan.is( 'span' ) || viewSpan.getStyle( 'display' ) != 'none' || viewSpan.childCount != 0 ) { + // The child must be the "span" element that is not displayed and has a space inside. + if ( !viewSpan.is( 'span' ) || viewSpan.getStyle( 'display' ) != 'none' || viewSpan.childCount != 1 ) { + return; + } + + const text = first( viewSpan.getChildren() ); + + if ( !text.is( 'text' ) || text.data !== ' ' ) { return; } diff --git a/tests/pagebreakediting.js b/tests/pagebreakediting.js index 1e446c0..23606b2 100644 --- a/tests/pagebreakediting.js +++ b/tests/pagebreakediting.js @@ -82,7 +82,7 @@ describe( 'PageBreakEditing', () => { editor.setData( '
 
' ); expect( getModelData( model, { withoutSelection: true } ) ) - .to.equal( '
' ); + .to.equal( '
' ); } ); it( 'should not convert if outer div has wrong styles', () => { @@ -186,6 +186,20 @@ describe( 'PageBreakEditing', () => { expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '
<$text foo="true">Foo
' ); } ); + + it( 'should not convert if inner span has no children', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if inner span has other element as a child', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); } ); } ); From f6cd2c8b86ffe3086e2d3e255d7fa7378befba37 Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Fri, 27 Sep 2019 08:54:17 +0200 Subject: [PATCH 09/12] Changed CTA in sample. --- docs/_snippets/features/page-break.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_snippets/features/page-break.html b/docs/_snippets/features/page-break.html index 98d2406..226f50f 100644 --- a/docs/_snippets/features/page-break.html +++ b/docs/_snippets/features/page-break.html @@ -5,7 +5,7 @@

What is Lorem Ipsum?

It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

- +