Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-engine

Package Overview
Dependencies
Maintainers
1
Versions
584
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-engine - npm Package Compare versions

Comparing version 30.0.0 to 31.0.0

theme/renderer.css

42

package.json
{
"name": "@ckeditor/ckeditor5-engine",
"version": "30.0.0",
"version": "31.0.0",
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",

@@ -26,24 +26,26 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-utils": "^30.0.0",
"@ckeditor/ckeditor5-utils": "^31.0.0",
"lodash-es": "^4.17.15"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^30.0.0",
"@ckeditor/ckeditor5-block-quote": "^30.0.0",
"@ckeditor/ckeditor5-clipboard": "^30.0.0",
"@ckeditor/ckeditor5-core": "^30.0.0",
"@ckeditor/ckeditor5-editor-classic": "^30.0.0",
"@ckeditor/ckeditor5-enter": "^30.0.0",
"@ckeditor/ckeditor5-essentials": "^30.0.0",
"@ckeditor/ckeditor5-heading": "^30.0.0",
"@ckeditor/ckeditor5-image": "^30.0.0",
"@ckeditor/ckeditor5-link": "^30.0.0",
"@ckeditor/ckeditor5-list": "^30.0.0",
"@ckeditor/ckeditor5-paragraph": "^30.0.0",
"@ckeditor/ckeditor5-table": "^30.0.0",
"@ckeditor/ckeditor5-theme-lark": "^30.0.0",
"@ckeditor/ckeditor5-typing": "^30.0.0",
"@ckeditor/ckeditor5-ui": "^30.0.0",
"@ckeditor/ckeditor5-undo": "^30.0.0",
"@ckeditor/ckeditor5-widget": "^30.0.0",
"@ckeditor/ckeditor5-basic-styles": "^31.0.0",
"@ckeditor/ckeditor5-block-quote": "^31.0.0",
"@ckeditor/ckeditor5-clipboard": "^31.0.0",
"@ckeditor/ckeditor5-cloud-services": "^31.0.0",
"@ckeditor/ckeditor5-core": "^31.0.0",
"@ckeditor/ckeditor5-editor-classic": "^31.0.0",
"@ckeditor/ckeditor5-enter": "^31.0.0",
"@ckeditor/ckeditor5-essentials": "^31.0.0",
"@ckeditor/ckeditor5-heading": "^31.0.0",
"@ckeditor/ckeditor5-image": "^31.0.0",
"@ckeditor/ckeditor5-link": "^31.0.0",
"@ckeditor/ckeditor5-list": "^31.0.0",
"@ckeditor/ckeditor5-mention": "^31.0.0",
"@ckeditor/ckeditor5-paragraph": "^31.0.0",
"@ckeditor/ckeditor5-table": "^31.0.0",
"@ckeditor/ckeditor5-theme-lark": "^31.0.0",
"@ckeditor/ckeditor5-typing": "^31.0.0",
"@ckeditor/ckeditor5-ui": "^31.0.0",
"@ckeditor/ckeditor5-undo": "^31.0.0",
"@ckeditor/ckeditor5-widget": "^31.0.0",
"webpack": "^4.43.0",

@@ -50,0 +52,0 @@ "webpack-cli": "^3.3.11"

@@ -148,2 +148,3 @@ /**

this.decorate( 'set' );
this.decorate( 'get' );

@@ -167,2 +168,3 @@ // Fire the `ready` event when the initialization has completed. Such low-level listener gives possibility

*
* @fires get
* @param {Object} [options] Additional configuration for the retrieved data. `DataController` provides two optional

@@ -528,2 +530,11 @@ * properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.

*/
/**
* Event fired after {@link #get get() method} has been run.
*
* The `get` event is fired by decorated {@link #get} method.
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
*
* @event get
*/
}

@@ -565,3 +576,41 @@

return result;
// Sort the markers in a stable fashion to ensure that the order that they are
// added to the model's marker collection does not affect how they are
// downcast. One particular use case that we're targeting here is one where
// two markers are adjacent but not overlapping, such as an insertion/deletion
// suggestion pair represting the replacement of a range of text. In this
// case, putting the markers in DOM order causes the first marker's end to be
// serialized right after the second marker's start, while putting the markers
// in reverse DOM order causes it to be right before the second marker's
// start. So, we sort in a way that ensures non-intersecting ranges are in
// reverse DOM order, and intersecting ranges are in something approximating
// reverse DOM order (since reverse DOM order doesn't have a precise meaning
// when working with intersectng ranges).
return result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
if ( r1.end.compareWith( r2.start ) !== 'after' ) {
// m1.end <= m2.start -- m1 is entirely <= m2
return 1;
} else if ( r1.start.compareWith( r2.end ) !== 'before' ) {
// m1.start >= m2.end -- m1 is entirely >= m2
return -1;
} else {
// they overlap, so use their start positions as the primary sort key and
// end positions as the secondary sort key
switch ( r1.start.compareWith( r2.start ) ) {
case 'before':
return 1;
case 'after':
return -1;
default:
switch ( r1.end.compareWith( r2.end ) ) {
case 'before':
return 1;
case 'after':
return -1;
default:
return n2.localeCompare( n1 );
}
}
}
} );
}

@@ -31,6 +31,5 @@ /**

*
* @private
* @member {DOMParser}
*/
this._domParser = new DOMParser();
this.domParser = new DOMParser();

@@ -40,6 +39,5 @@ /**

*
* @private
* @member {module:engine/view/domconverter~DomConverter}
*/
this._domConverter = new DomConverter( document, { blockFillerMode: 'nbsp' } );
this.domConverter = new DomConverter( document, { renderingMode: 'data' } );

@@ -49,6 +47,5 @@ /**

*
* @private
* @member {module:engine/dataprocessor/basichtmlwriter~BasicHtmlWriter}
* @member {module:engine/dataprocessor/htmlwriter~HtmlWriter}
*/
this._htmlWriter = new BasicHtmlWriter();
this.htmlWriter = new BasicHtmlWriter();
}

@@ -65,6 +62,6 @@

// Convert view DocumentFragment to DOM DocumentFragment.
const domFragment = this._domConverter.viewToDom( viewFragment, document );
const domFragment = this.domConverter.viewToDom( viewFragment, document );
// Convert DOM DocumentFragment to HTML output.
return this._htmlWriter.getHtml( domFragment );
return this.htmlWriter.getHtml( domFragment );
}

@@ -83,3 +80,3 @@

// Convert DOM DocumentFragment to view DocumentFragment.
return this._domConverter.domToView( domFragment );
return this.domConverter.domToView( domFragment );
}

@@ -98,3 +95,3 @@

registerRawContentMatcher( pattern ) {
this._domConverter.registerRawContentMatcher( pattern );
this.domConverter.registerRawContentMatcher( pattern );
}

@@ -114,3 +111,3 @@

useFillerType( type ) {
this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
}

@@ -127,3 +124,3 @@

_toDom( data ) {
const document = this._domParser.parseFromString( data, 'text/html' );
const document = this.domParser.parseFromString( data, 'text/html' );
const fragment = document.createDocumentFragment();

@@ -130,0 +127,0 @@

@@ -29,3 +29,3 @@ /**

* @param {Object} options Configuration options.
* @param {Array<String>} [options.namespaces=[]] A list of namespaces allowed to use in the XML input.
* @param {Array.<String>} [options.namespaces=[]] A list of namespaces allowed to use in the XML input.
*/

@@ -39,4 +39,3 @@ constructor( document, options = {} ) {

*
* @public
* @member {DOMParser}
* @member {Array.<String>}
*/

@@ -48,6 +47,5 @@ this.namespaces = options.namespaces || [];

*
* @private
* @member {DOMParser}
*/
this._domParser = new DOMParser();
this.domParser = new DOMParser();

@@ -57,6 +55,5 @@ /**

*
* @private
* @member {module:engine/view/domconverter~DomConverter}
*/
this._domConverter = new DomConverter( document, { blockFillerMode: 'nbsp' } );
this.domConverter = new DomConverter( document, { renderingMode: 'data' } );

@@ -67,6 +64,5 @@ /**

*
* @private
* @member {module:engine/dataprocessor/basichtmlwriter~BasicHtmlWriter}
* @member {module:engine/dataprocessor/htmlwriter~HtmlWriter}
*/
this._htmlWriter = new BasicHtmlWriter();
this.htmlWriter = new BasicHtmlWriter();
}

@@ -83,7 +79,7 @@

// Convert view DocumentFragment to DOM DocumentFragment.
const domFragment = this._domConverter.viewToDom( viewFragment, document );
const domFragment = this.domConverter.viewToDom( viewFragment, document );
// Convert DOM DocumentFragment to XML output.
// There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
return this._htmlWriter.getHtml( domFragment );
return this.htmlWriter.getHtml( domFragment );
}

@@ -102,3 +98,3 @@

// Convert DOM DocumentFragment to view DocumentFragment.
return this._domConverter.domToView( domFragment, { keepOriginalCase: true } );
return this.domConverter.domToView( domFragment, { keepOriginalCase: true } );
}

@@ -117,3 +113,3 @@

registerRawContentMatcher( pattern ) {
this._domConverter.registerRawContentMatcher( pattern );
this.domConverter.registerRawContentMatcher( pattern );
}

@@ -133,3 +129,3 @@

useFillerType( type ) {
this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
}

@@ -152,3 +148,3 @@

const parsedDocument = this._domParser.parseFromString( data, 'text/xml' );
const parsedDocument = this.domParser.parseFromString( data, 'text/xml' );

@@ -155,0 +151,0 @@ // Parse validation.

@@ -42,2 +42,9 @@ /**

};
// Returns simplified implementation of {@link module:engine/view/domconverter~DomConverter#setContentOf DomConverter.setContentOf} method.
// Used to render UIElement and RawElement.
const domConverterStub = {
setContentOf: ( node, html ) => {
node.innerHTML = html;
}
};

@@ -63,2 +70,5 @@ /**

* {@link module:engine/view/rawelement~RawElement} will be printed.
* @param {Object} [options.domConverter=null] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
* instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
* filtering. Otherwise the simple stub is used.
* @returns {String} The stringified data.

@@ -80,3 +90,4 @@ */

renderRawElements: options.renderRawElements,
ignoreRoot: true
ignoreRoot: true,
domConverter: options.domConverter
};

@@ -247,2 +258,5 @@

* {@link module:engine/view/rawelement~RawElement} will be printed.
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
* instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
* filtering. Otherwise the simple stub is used.
* @returns {String} An HTML-like string representing the view.

@@ -637,2 +651,5 @@ */

* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
* instance it lets the conversion go through exactly the same flow the editing view is going, i.e. with view data
* filtering. Otherwise the simple stub is used.
* {@link module:engine/view/rawelement~RawElement} will be printed.

@@ -656,2 +673,3 @@ */

this.renderRawElements = !!options.renderRawElements;
this.domConverter = options.domConverter || domConverterStub;
}

@@ -690,3 +708,3 @@

if ( ( this.renderUIElements && root.is( 'uiElement' ) ) ) {
callback( root.render( document ).innerHTML );
callback( root.render( document, this.domConverter ).innerHTML );
} else if ( this.renderRawElements && root.is( 'rawElement' ) ) {

@@ -696,3 +714,3 @@ // There's no DOM element for "root" to pass to render(). Creating

const rawContentContainer = document.createElement( 'div' );
root.render( rawContentContainer );
root.render( rawContentContainer, this.domConverter );

@@ -699,0 +717,0 @@ callback( rawContentContainer.innerHTML );

@@ -131,5 +131,13 @@ /**

// This might be null ie when editor data is empty.
// In such cases there is no need to fix the selection range.
// This might be null ie when editor data is empty or the selection is inside limit element
// that doesn't allow text inside.
// In the first case there is no need to fix the selection range.
// In the second let's go up to the outer selectable element
if ( !nearestSelectionRange ) {
const ancestorObject = originalPosition.getAncestors().reverse().find( item => schema.isObject( item ) );
if ( ancestorObject ) {
return Range._createOn( ancestorObject );
}
return null;

@@ -136,0 +144,0 @@ }

@@ -84,2 +84,14 @@ /**

/**
* `true` while the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
* When they stop selecting, the property goes back to `false`.
*
* This property is updated by the {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
*
* @readonly
* @observable
* @member {Boolean} module:engine/view/document~Document#isSelecting
*/
this.set( 'isSelecting', false );
/**
* True if composition is in progress inside the document.

@@ -86,0 +98,0 @@ *

@@ -10,3 +10,3 @@ /**

/* globals document, Node, Text */
/* globals document, Node, NodeFilter, DOMParser, Text */

@@ -55,3 +55,8 @@ import ViewText from './text';

* @param {Object} options An object with configuration options.
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode='br'] The type of the block filler to use.
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode] The type of the block filler to use.
* Default value depends on the options.renderingMode:
* 'nbsp' when options.renderingMode == 'data',
* 'br' when options.renderingMode == 'editing'.
* @param {'data'|'editing'} [options.renderingMode='editing'] Whether to leave the View-to-DOM conversion result unchanged
* or improve editing experience by filtering out interactive data.
*/

@@ -66,2 +71,17 @@ constructor( document, options = {} ) {

/**
* Whether to leave the View-to-DOM conversion result unchanged or improve editing experience by filtering out interactive data.
*
* @member {'data'|'editing'} module:engine/view/domconverter~DomConverter#renderingMode
*/
this.renderingMode = options.renderingMode || 'editing';
/**
* Main switch for new rendering approach in the editing view.
*
* @protected
* @member {Boolean}
*/
this.experimentalRenderingMode = false;
/**
* The mode of a block filler used by the DOM converter.

@@ -71,3 +91,3 @@ *

*/
this.blockFillerMode = options.blockFillerMode || 'br';
this.blockFillerMode = options.blockFillerMode || ( this.renderingMode === 'editing' ? 'br' : 'nbsp' );

@@ -229,2 +249,78 @@ /**

/**
* Decides whether given pair of attribute key and value should be passed further down the pipeline.
*
* @param {String} attributeKey
* @param {String} attributeValue
* @returns {Boolean}
*/
shouldRenderAttribute( attributeKey, attributeValue ) {
if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
return true;
}
return !( attributeKey.toLowerCase().startsWith( 'on' ) ||
attributeValue.match( /(\b)(on\S+)(\s*)=|javascript:|(<\s*)(\/*)script/i ) ||
attributeValue.match( /data:(?!image\/(png|jpeg|gif|webp))/i )
);
}
/**
* Set `domElement`'s content using provided `html` argument. Apply necessary filtering for the editing pipeline.
*
* @param {Element} domElement DOM element that should have `html` set as its content.
* @param {String} html Textual representation of the HTML that will be set on `domElement`.
*/
setContentOf( domElement, html ) {
// For data pipeline we pass the HTML as-is.
if ( !this.experimentalRenderingMode || this.renderingMode === 'data' ) {
domElement.innerHTML = html;
return;
}
const document = new DOMParser().parseFromString( html, 'text/html' );
const fragment = document.createDocumentFragment();
const bodyChildNodes = document.body.childNodes;
while ( bodyChildNodes.length > 0 ) {
fragment.appendChild( bodyChildNodes[ 0 ] );
}
const treeWalker = document.createTreeWalker( fragment, NodeFilter.SHOW_ELEMENT );
const nodes = [];
let currentNode;
// eslint-disable-next-line no-cond-assign
while ( currentNode = treeWalker.nextNode() ) {
nodes.push( currentNode );
}
for ( const currentNode of nodes ) {
// Go through nodes to remove those that are prohibited in editing pipeline.
for ( const attributeName of currentNode.getAttributeNames() ) {
const attributeValue = currentNode.getAttribute( attributeName );
if ( !this.shouldRenderAttribute( attributeName, attributeValue ) ) {
currentNode.removeAttribute( attributeName );
}
}
const elementName = currentNode.tagName.toLowerCase();
// There are certain nodes, that should be renamed to <span> in editing pipeline.
if ( this._shouldRenameElement( elementName ) ) {
currentNode.replaceWith( this._createReplacementDomElement( elementName, currentNode ) );
}
}
// Empty the target element.
while ( domElement.firstChild ) {
domElement.firstChild.remove();
}
domElement.append( fragment );
}
/**
* Converts the view to the DOM. For all text nodes, not bound elements and document fragments new items will

@@ -265,3 +361,3 @@ * be created. For bound elements and document fragments the method will return corresponding items.

// UIElement has its own render() method (see #799).
domElement = viewNode.render( domDocument );
domElement = viewNode.render( domDocument, this );
}

@@ -276,3 +372,5 @@

// Create DOM element.
if ( viewNode.hasAttribute( 'xmlns' ) ) {
if ( this._shouldRenameElement( viewNode.name ) ) {
domElement = this._createReplacementDomElement( viewNode.name );
} else if ( viewNode.hasAttribute( 'xmlns' ) ) {
domElement = domDocument.createElementNS( viewNode.getAttribute( 'xmlns' ), viewNode.name );

@@ -286,3 +384,3 @@ } else {

if ( viewNode.is( 'rawElement' ) ) {
viewNode.render( domElement );
viewNode.render( domElement, this );
}

@@ -296,3 +394,9 @@

for ( const key of viewNode.getAttributeKeys() ) {
domElement.setAttribute( key, viewNode.getAttribute( key ) );
const value = viewNode.getAttribute( key );
if ( !this.shouldRenderAttribute( key, value ) ) {
continue;
}
domElement.setAttribute( key, value );
}

@@ -615,6 +719,6 @@ }

* @param {Node} domParent DOM position parent.
* @param {Number} domOffset DOM position offset.
* @param {Number} [domOffset=0] DOM position offset. You can skip it when converting the inline filler node.
* @returns {module:engine/view/position~Position} viewPosition View position.
*/
domPositionToView( domParent, domOffset ) {
domPositionToView( domParent, domOffset = 0 ) {
if ( this.isBlockFiller( domParent ) ) {

@@ -1386,2 +1490,41 @@ return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );

}
/**
* Checks whether given element name should be renamed in a current rendering mode.
*
* @private
* @param {String} elementName The name of view element.
* @returns {Boolean}
*/
_shouldRenameElement( elementName ) {
return this.experimentalRenderingMode && this.renderingMode == 'editing' && elementName == 'script';
}
/**
* Return a <span> element with special attribute holding the name of the original element.
* Optionally, copy all the attributes of the original element if that element is provided.
*
* @private
* @param {String} elementName The name of view element.
* @param {Element} [originalDomElement] The original DOM element to copy attributes and content from.
* @returns {Element}
*/
_createReplacementDomElement( elementName, originalDomElement = null ) {
const newDomElement = document.createElement( 'span' );
// Mark the span replacing a script as hidden.
newDomElement.setAttribute( 'data-ck-hidden', elementName );
if ( originalDomElement ) {
while ( originalDomElement.firstChild ) {
newDomElement.appendChild( originalDomElement.firstChild );
}
for ( const attributeName of originalDomElement.getAttributeNames() ) {
newDomElement.setAttribute( attributeName, originalDomElement.getAttribute( attributeName ) );
}
}
return newDomElement;
}
}

@@ -1388,0 +1531,0 @@

@@ -23,2 +23,4 @@ /**

*
* This observer also manages the {@link module:engine/view/document~Document#isSelecting} property of the view document.
*
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.

@@ -82,5 +84,23 @@ *

/**
* When called, starts clearing the {@link #_loopbackCounter} counter in intervals of time. When the number of selection
* changes exceeds a certain limit within the interval of time, the observer will not fire `selectionChange` but warn about
* possible infinite selection loop.
*
* @private
* @member {Number} #_clearInfiniteLoopInterval
*/
this._clearInfiniteLoopInterval = setInterval( () => this._clearInfiniteLoop(), 1000 );
/**
* Unlocks the `isSelecting` state of the view document in case the selection observer did not record this fact
* correctly (for whatever the reason). It is a safeguard (paranoid check) that returns document to the normal state
* after a certain period of time (debounced, postponed by each selectionchange event).
*
* @private
* @method #_documentIsSelectingInactivityTimeoutDebounced
*/
this._documentIsSelectingInactivityTimeoutDebounced = debounce( () => ( this.document.isSelecting = false ), 5000 );
/**
* Private property to check if the code does not enter infinite loop.

@@ -100,3 +120,24 @@ *

// Add listener once per each document.
const startDocumentIsSelecting = () => {
this.document.isSelecting = true;
// Let's activate the safety timeout each time the document enters the "is selecting" state.
this._documentIsSelectingInactivityTimeoutDebounced();
};
const endDocumentIsSelecting = () => {
this.document.isSelecting = false;
// The safety timeout can be canceled when the document leaves the "is selecting" state.
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
};
// The document has the "is selecting" state while the user keeps making (extending) the selection
// (e.g. by holding the mouse button and moving the cursor). The state resets when they either released
// the mouse button or interrupted the process by pressing or releasing any key.
this.listenTo( domElement, 'selectstart', startDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domElement, 'keydown', endDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domElement, 'keyup', endDocumentIsSelecting, { priority: 'highest' } );
// Add document-wide listeners only once. This method could be called for multiple editing roots.
if ( this._documents.has( domDocument ) ) {

@@ -106,4 +147,9 @@ return;

this.listenTo( domDocument, 'mouseup', endDocumentIsSelecting, { priority: 'highest' } );
this.listenTo( domDocument, 'selectionchange', ( evt, domEvent ) => {
this._handleSelectionChange( domEvent, domDocument );
// Defer the safety timeout when the selection changes (e.g. the user keeps extending the selection
// using their mouse).
this._documentIsSelectingInactivityTimeoutDebounced();
} );

@@ -122,2 +168,3 @@

this._fireSelectionChangeDoneDebounced.cancel();
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
}

@@ -124,0 +171,0 @@

@@ -134,4 +134,4 @@ /**

*
* myRawElement.render = function( domElement ) {
* domElement.innerHTML = '<b>This is the raw content of myRawElement.</b>';
* myRawElement.render = function( domElement, domConverter ) {
* domConverter.setContentOf( domElement, '<b>This is the raw content of myRawElement.</b>' );
* };

@@ -141,2 +141,3 @@ *

* @param {HTMLElement} domElement The native DOM element representing the raw view element.
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
*/

@@ -143,0 +144,0 @@ }

@@ -27,2 +27,4 @@ /**

import '../../theme/renderer.css';
/**

@@ -102,6 +104,32 @@ * Renderer is responsible for updating the DOM structure and the DOM selection based on

* @member {Boolean}
* @observable
*/
this.isFocused = false;
this.set( 'isFocused', false );
/**
* Indicates whether the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
* When they stop selecting, the property goes back to `false`.
*
* Note: In some browsers, the renderer will stop rendering the selection and inline fillers while the user is making
* a selection to avoid glitches in DOM selection
* (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
*
* @member {Boolean}
* @observable
*/
this.set( 'isSelecting', false );
// Rendering the selection and inline filler manipulation should be postponed in (non-Android) Blink until the user finishes
// creating the selection in DOM to avoid accidental selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
// When the user stops, selecting, all pending changes should be rendered ASAP, though.
if ( env.isBlink && !env.isAndroid ) {
this.on( 'change:isSelecting', () => {
if ( !this.isSelecting ) {
this.render();
}
} );
}
/**
* The text node in which the inline filler was rendered.

@@ -175,2 +203,3 @@ *

let inlineFillerPosition;
const isInlineFillerRenderingPossible = env.isBlink && !env.isAndroid ? !this.isSelecting : true;

@@ -182,20 +211,31 @@ // Refresh mappings.

// There was inline filler rendered in the DOM but it's not
// at the selection position any more, so we can remove it
// (cause even if it's needed, it must be placed in another location).
if ( this._inlineFiller && !this._isSelectionInInlineFiller() ) {
this._removeInlineFiller();
}
// Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
// DOM selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
if ( isInlineFillerRenderingPossible ) {
// There was inline filler rendered in the DOM but it's not
// at the selection position any more, so we can remove it
// (cause even if it's needed, it must be placed in another location).
if ( this._inlineFiller && !this._isSelectionInInlineFiller() ) {
this._removeInlineFiller();
}
// If we've got the filler, let's try to guess its position in the view.
if ( this._inlineFiller ) {
inlineFillerPosition = this._getInlineFillerPosition();
}
// Otherwise, if it's needed, create it at the selection position.
else if ( this._needsInlineFillerAtSelection() ) {
inlineFillerPosition = this.selection.getFirstPosition();
// If we've got the filler, let's try to guess its position in the view.
if ( this._inlineFiller ) {
inlineFillerPosition = this._getInlineFillerPosition();
}
// Otherwise, if it's needed, create it at the selection position.
else if ( this._needsInlineFillerAtSelection() ) {
inlineFillerPosition = this.selection.getFirstPosition();
// Do not use `markToSync` so it will be added even if the parent is already added.
this.markedChildren.add( inlineFillerPosition.parent );
// Do not use `markToSync` so it will be added even if the parent is already added.
this.markedChildren.add( inlineFillerPosition.parent );
}
}
// Paranoid check: we make sure the inline filler has any parent so it can be mapped to view position
// by DomConverter.
else if ( this._inlineFiller && this._inlineFiller.parentNode ) {
// While the user is making selection, preserve the inline filler at its original position.
inlineFillerPosition = this.domConverter.domPositionToView( this._inlineFiller );
}

@@ -216,22 +256,26 @@ for ( const element of this.markedAttributes ) {

// Check whether the inline filler is required and where it really is in the DOM.
// At this point in most cases it will be in the DOM, but there are exceptions.
// For example, if the inline filler was deep in the created DOM structure, it will not be created.
// Similarly, if it was removed at the beginning of this function and then neither text nor children were updated,
// it will not be present.
// Fix those and similar scenarios.
if ( inlineFillerPosition ) {
const fillerDomPosition = this.domConverter.viewPositionToDom( inlineFillerPosition );
const domDocument = fillerDomPosition.parent.ownerDocument;
// * Check whether the inline filler is required and where it really is in the DOM.
// At this point in most cases it will be in the DOM, but there are exceptions.
// For example, if the inline filler was deep in the created DOM structure, it will not be created.
// Similarly, if it was removed at the beginning of this function and then neither text nor children were updated,
// it will not be present. Fix those and similar scenarios.
// * Don't manipulate inline fillers while the selection is being made in (non-Android) Blink to prevent accidental
// DOM selection collapsing
// (https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723).
if ( isInlineFillerRenderingPossible ) {
if ( inlineFillerPosition ) {
const fillerDomPosition = this.domConverter.viewPositionToDom( inlineFillerPosition );
const domDocument = fillerDomPosition.parent.ownerDocument;
if ( !startsWithFiller( fillerDomPosition.parent ) ) {
// Filler has not been created at filler position. Create it now.
this._inlineFiller = addInlineFiller( domDocument, fillerDomPosition.parent, fillerDomPosition.offset );
if ( !startsWithFiller( fillerDomPosition.parent ) ) {
// Filler has not been created at filler position. Create it now.
this._inlineFiller = addInlineFiller( domDocument, fillerDomPosition.parent, fillerDomPosition.offset );
} else {
// Filler has been found, save it.
this._inlineFiller = fillerDomPosition.parent;
}
} else {
// Filler has been found, save it.
this._inlineFiller = fillerDomPosition.parent;
// There is no filler needed.
this._inlineFiller = null;
}
} else {
// There is no filler needed.
this._inlineFiller = null;
}

@@ -409,3 +453,3 @@

if ( isInlineFiller( domFillerNode ) ) {
domFillerNode.parentNode.removeChild( domFillerNode );
domFillerNode.remove();
} else {

@@ -520,3 +564,9 @@ domFillerNode.data = domFillerNode.data.substr( INLINE_FILLER_LENGTH );

for ( const key of viewAttrKeys ) {
domElement.setAttribute( key, viewElement.getAttribute( key ) );
const value = viewElement.getAttribute( key );
if ( !this.domConverter.shouldRenderAttribute( key, value ) ) {
domElement.removeAttribute( key );
} else {
domElement.setAttribute( key, value );
}
}

@@ -526,2 +576,8 @@

for ( const key of domAttrKeys ) {
// Do not remove attributes on `script` elements with special data attributes `data-ck-hidden`.
if ( viewElement.name === 'script' && key === 'data-ck-hidden' ) {
continue;
}
// All other attributes not present in the DOM should be removed.
if ( !viewElement.hasAttribute( key ) ) {

@@ -554,3 +610,3 @@ domElement.removeAttribute( key );

const expectedDomChildren = Array.from(
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { bind: true, inlineFillerPosition } )
this.domConverter.viewChildrenToDom( viewElement, domElement.ownerDocument, { bind: true } )
);

@@ -696,2 +752,10 @@

_updateSelection() {
// Block updating DOM selection in (non-Android) Blink while the user is selecting to prevent accidental selection collapsing.
// Note: Structural changes in DOM must trigger selection rendering, though. Nodes the selection was anchored
// to may disappear in DOM which would break the selection (e.g. in real-time collaboration scenarios).
// https://github.com/ckeditor/ckeditor5/issues/10562, https://github.com/ckeditor/ckeditor5/issues/10723
if ( env.isBlink && !env.isAndroid && this.isSelecting && !this.markedChildren.size ) {
return;
}
// If there is no selection - remove DOM and fake selections.

@@ -698,0 +762,0 @@ if ( this.selection.rangeCount === 0 ) {

@@ -129,6 +129,7 @@ /**

* const myUIElement = downcastWriter.createUIElement( 'span' );
* myUIElement.render = function( domDocument ) {
* myUIElement.render = function( domDocument, domConverter ) {
* const domElement = this.toDomElement( domDocument );
* domElement.innerHTML = '<b>this is ui element</b>';
*
* domConverter.setContentOf( domElement, '<b>this is ui element</b>' );
*
* return domElement;

@@ -142,5 +143,7 @@ * };

* @param {Document} domDocument
* @param {module:engine/view/domconverter~DomConverter} domConverter Instance of the DomConverter used to optimize the output.
* @returns {HTMLElement}
*/
render( domDocument ) {
// Provide basic, default output.
return this.toDomElement( domDocument );

@@ -147,0 +150,0 @@ }

@@ -119,3 +119,3 @@ /**

this._renderer = new Renderer( this.domConverter, this.document.selection );
this._renderer.bind( 'isFocused' ).to( this.document );
this._renderer.bind( 'isFocused', 'isSelecting' ).to( this.document );

@@ -122,0 +122,0 @@ /**

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc