diff --git a/packages/ckeditor5-ckbox/tests/ckboximageedit/ckboximageeditcommand.js b/packages/ckeditor5-ckbox/tests/ckboximageedit/ckboximageeditcommand.js index 95d4ffbd7dc..62e4a701151 100644 --- a/packages/ckeditor5-ckbox/tests/ckboximageedit/ckboximageeditcommand.js +++ b/packages/ckeditor5-ckbox/tests/ckboximageedit/ckboximageeditcommand.js @@ -951,7 +951,8 @@ describe( 'CKBoxImageEditCommand', () => { 'while waiting for the processed image', async () => { expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '
' + - 'alt text' + + 'alt text' + + '' + '
' + '
' ); @@ -961,7 +962,9 @@ describe( 'CKBoxImageEditCommand', () => { expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '
' + - 'alt text' + + 'alt text' + + '' + '
' + '
' ); diff --git a/packages/ckeditor5-engine/src/view/placeholder.ts b/packages/ckeditor5-engine/src/view/placeholder.ts index 4eea24a7275..38472a0a81b 100644 --- a/packages/ckeditor5-engine/src/view/placeholder.ts +++ b/packages/ckeditor5-engine/src/view/placeholder.ts @@ -50,24 +50,22 @@ export function enablePlaceholder( { view, element, text, isDirectHost = true, k } ): void { const doc = view.document; - // Use a single a single post fixer per—document to update all placeholders. + // Use a single post fixer per—document to update all placeholders. if ( !documentPlaceholders.has( doc ) ) { documentPlaceholders.set( doc, new Map() ); // If a post-fixer callback makes a change, it should return `true` so other post–fixers // can re–evaluate the document again. - doc.registerPostFixer( writer => updateDocumentPlaceholders( doc, writer ) ); + doc.registerPostFixer( writer => updateDocumentPlaceholders( documentPlaceholders.get( doc )!, writer ) ); // Update placeholders on isComposing state change since rendering is disabled while in composition mode. doc.on( 'change:isComposing', () => { - view.change( writer => updateDocumentPlaceholders( doc, writer ) ); + view.change( writer => updateDocumentPlaceholders( documentPlaceholders.get( doc )!, writer ) ); }, { priority: 'high' } ); } if ( element.is( 'editableElement' ) ) { - element.on( 'change:placeholder', ( evtInfo, evt, text ) => { - setPlaceholder( text ); - } ); + element.on( 'change:placeholder', ( evtInfo, evt, text ) => setPlaceholder( text ) ); } if ( element.placeholder ) { @@ -81,16 +79,18 @@ export function enablePlaceholder( { view, element, text, isDirectHost = true, k } function setPlaceholder( text: string ) { - // Store information about the element placeholder under its document. - documentPlaceholders.get( doc )!.set( element, { + const config = { text, isDirectHost, keepOnFocus, hostElement: isDirectHost ? element : null - } ); + }; + + // Store information about the element placeholder under its document. + documentPlaceholders.get( doc )!.set( element, config ); // Update the placeholders right away. - view.change( writer => updateDocumentPlaceholders( doc, writer ) ); + view.change( writer => updateDocumentPlaceholders( [ [ element, config ] ], writer ) ); } } @@ -182,11 +182,7 @@ export function needsPlaceholder( element: Element, keepOnFocus: boolean ): bool return false; } - // Anything but uiElement(s) counts as content. - const hasContent = Array.from( element.getChildren() ) - .some( element => !element.is( 'uiElement' ) ); - - if ( hasContent ) { + if ( hasContent( element ) ) { return false; } @@ -212,13 +208,28 @@ export function needsPlaceholder( element: Element, keepOnFocus: boolean ): bool return !!selectionAnchor && selectionAnchor.parent !== element; } +/** + * Anything but uiElement(s) counts as content. + */ +function hasContent( element: Element ): boolean { + for ( const child of element.getChildren() ) { + if ( !child.is( 'uiElement' ) ) { + return true; + } + } + + return false; +} + /** * Updates all placeholders associated with a document in a post–fixer callback. * * @returns True if any changes were made to the view document. */ -function updateDocumentPlaceholders( doc: Document, writer: DowncastWriter ): boolean { - const placeholders = documentPlaceholders.get( doc )!; +function updateDocumentPlaceholders( + placeholders: Iterable<[ Element, PlaceholderConfig ]>, + writer: DowncastWriter +): boolean { const directHostElements: Array = []; let wasViewModified = false; diff --git a/packages/ckeditor5-engine/tests/view/placeholder.js b/packages/ckeditor5-engine/tests/view/placeholder.js index 9c3f09df116..09967c718ce 100644 --- a/packages/ckeditor5-engine/tests/view/placeholder.js +++ b/packages/ckeditor5-engine/tests/view/placeholder.js @@ -288,6 +288,8 @@ describe( 'placeholder', () => { isDirectHost: true } ); + view.forceRender(); + expect( viewRoot.getChild( 0 ).getAttribute( 'data-placeholder' ) ).to.equal( 'bar' ); expect( viewRoot.getChild( 0 ).isEmpty ).to.be.true; expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.true; diff --git a/packages/ckeditor5-image/src/imagesizeattributes.ts b/packages/ckeditor5-image/src/imagesizeattributes.ts index 165659d6fd4..51937eb2f38 100644 --- a/packages/ckeditor5-image/src/imagesizeattributes.ts +++ b/packages/ckeditor5-image/src/imagesizeattributes.ts @@ -121,8 +121,8 @@ export default class ImageSizeAttributes extends Plugin { // Dedicated converters to propagate attributes to the element. editor.conversion.for( 'editingDowncast' ).add( dispatcher => { - attachDowncastConverter( dispatcher, 'width', 'width', true ); - attachDowncastConverter( dispatcher, 'height', 'height', true ); + attachDowncastConverter( dispatcher, 'width', 'width', true, true ); + attachDowncastConverter( dispatcher, 'height', 'height', true, true ); } ); editor.conversion.for( 'dataDowncast' ).add( dispatcher => { @@ -134,7 +134,8 @@ export default class ImageSizeAttributes extends Plugin { dispatcher: DowncastDispatcher, modelAttributeName: string, viewAttributeName: string, - setRatioForInlineImage: boolean + setRatioForInlineImage: boolean, + isEditingDowncast: boolean = false ) { dispatcher.on( `attribute:${ modelAttributeName }:${ imageType }`, ( evt, data, conversionApi ) => { if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { @@ -166,8 +167,14 @@ export default class ImageSizeAttributes extends Plugin { const width = data.item.getAttribute( 'width' ); const height = data.item.getAttribute( 'height' ); - if ( width && height ) { - viewWriter.setStyle( 'aspect-ratio', `${ width }/${ height }`, img ); + if ( !width || !height ) { + return; + } + + viewWriter.setStyle( 'aspect-ratio', `${ width }/${ height }`, img ); + + if ( isEditingDowncast ) { + viewWriter.setAttribute( 'loading', 'lazy', img ); } } ); } diff --git a/packages/ckeditor5-image/tests/imagesizeattributes.js b/packages/ckeditor5-image/tests/imagesizeattributes.js index 75f5d83b6a4..7ddb3f13576 100644 --- a/packages/ckeditor5-image/tests/imagesizeattributes.js +++ b/packages/ckeditor5-image/tests/imagesizeattributes.js @@ -290,7 +290,8 @@ describe( 'ImageSizeAttributes', () => { expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '

' + - '' + + '' + + '' + '

' ); @@ -307,7 +308,8 @@ describe( 'ImageSizeAttributes', () => { expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '

' + - '' + + '' + + '' + '

' ); @@ -489,7 +491,8 @@ describe( 'ImageSizeAttributes', () => { expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + - '' + + '' + + '' + '
' ); @@ -507,7 +510,8 @@ describe( 'ImageSizeAttributes', () => { expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + - '' + + '' + + '' + '
' ); diff --git a/tests/_data/data-sets/ghs.js b/tests/_data/data-sets/ghs.js new file mode 100644 index 00000000000..3c495d5ed5f --- /dev/null +++ b/tests/_data/data-sets/ghs.js @@ -0,0 +1,83 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options + */ + +// This is main Wikipedia page source copied four times. This is to test content with a lot of messy / unsupported markup. +// After it is loaded in the editor, it is ~150 A4 pages (however there are a lot of very short paragraphs and list items). + +/* eslint-disable */ + +const initialData = + `

Feature paragraph

+ +

Feature heading1

+

Feature heading2

+

Feature heading3

+ +

Feature bold

+

Feature italic

+

Feature strike

+

Feature underline

+

Feature code

+

Feature subscript

+

Feature superscript

+ +

Link feature

+

Anchor (name, ID only)

+ +

Mark feature

+ +

Font feature

+ +
    +
  • Bulleted List feature
  • +
  • Bulleted List feature
  • +
  • Bulleted List feature
  • +
+ +
    +
  1. Numbered List feature
  2. +
  3. Numbered List feature
  4. +
  5. Numbered List feature
  6. +
+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +

Blockquote Feature

+ +
+ +
Caption
+
+ +
Code Block
+ +
HTML snippet
+
 
+

empty inline at start

+

Text with empty inline inside

+

Text with empty inline at the end

`; + +export default function makeData() { + return initialData.repeat( 250 ); +} diff --git a/tests/_data/data-sets/index.js b/tests/_data/data-sets/index.js index 34bacb88260..939586c814f 100644 --- a/tests/_data/data-sets/index.js +++ b/tests/_data/data-sets/index.js @@ -3,14 +3,22 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ -import paragraphs from './paragraphs.js'; -import lists from './lists.js'; -import tableHuge from './table-huge.js'; import formattingLongP from './formatting-long-paragraphs.js'; +import ghs from './ghs.js'; import inlineStyles from './inline-styles.js'; +import lists from './lists.js'; import mixed from './mixed.js'; +import paragraphs from './paragraphs.js'; +import tableHuge from './table-huge.js'; import wiki from './wiki.js'; export default { - paragraphs, lists, tableHuge, formattingLongP, inlineStyles, mixed, wiki + formattingLongP, + ghs, + inlineStyles, + lists, + mixed, + paragraphs, + tableHuge, + wiki };