@ckeditor/ckeditor5-engine
Advanced tools
Comparing version 37.0.0-alpha.3 to 37.0.0-rc.0
{ | ||
"name": "@ckeditor/ckeditor5-engine", | ||
"version": "37.0.0-alpha.3", | ||
"version": "37.0.0-rc.0", | ||
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.", | ||
@@ -26,26 +26,26 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-utils": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-utils": "^37.0.0-rc.0", | ||
"lodash-es": "^4.17.15" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-basic-styles": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-block-quote": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-clipboard": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-cloud-services": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-core": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-enter": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-essentials": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-heading": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-image": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-link": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-list": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-mention": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-paragraph": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-table": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-typing": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-ui": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-undo": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-widget": "^37.0.0-alpha.3", | ||
"@ckeditor/ckeditor5-basic-styles": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-block-quote": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-clipboard": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-cloud-services": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-core": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-enter": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-essentials": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-heading": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-image": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-link": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-list": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-mention": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-paragraph": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-table": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-theme-lark": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-typing": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-ui": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-undo": "^37.0.0-rc.0", | ||
"@ckeditor/ckeditor5-widget": "^37.0.0-rc.0", | ||
"typescript": "^4.8.4", | ||
@@ -56,3 +56,3 @@ "webpack": "^5.58.1", | ||
"engines": { | ||
"node": ">=14.0.0", | ||
"node": ">=16.0.0", | ||
"npm": ">=5.7.1" | ||
@@ -59,0 +59,0 @@ }, |
@@ -93,2 +93,5 @@ /** | ||
* | ||
* A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should | ||
* be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string. | ||
* | ||
* @fires get | ||
@@ -95,0 +98,0 @@ * @param options Additional configuration for the retrieved data. `DataController` provides two optional |
@@ -20,2 +20,3 @@ /** | ||
import HtmlDataProcessor from '../dataprocessor/htmldataprocessor'; | ||
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; | ||
/** | ||
@@ -91,2 +92,5 @@ * Controller for the data pipeline. The data pipeline controls how data is retrieved from the document | ||
* | ||
* A warning is logged when you try to retrieve data for a detached root, as most probably this is a mistake. A detached root should | ||
* be treated like it is removed, and you should not save its data. Note, that the detached root data is always an empty string. | ||
* | ||
* @fires get | ||
@@ -121,2 +125,13 @@ * @param options Additional configuration for the retrieved data. `DataController` provides two optional | ||
const root = this.model.document.getRoot(rootName); | ||
if (!root.isAttached()) { | ||
/** | ||
* Retrieving document data for a detached root. | ||
* | ||
* This usually indicates an error as a detached root should be considered "removed" and should not be included in the | ||
* document data. | ||
* | ||
* @error datacontroller-get-detached-root | ||
*/ | ||
logWarning('datacontroller-get-detached-root', this); | ||
} | ||
if (trim === 'empty' && !this.model.hasContent(root, { ignoreWhitespaces: true })) { | ||
@@ -393,3 +408,3 @@ return ''; | ||
for (const rootName of rootNames) { | ||
if (!this.model.document.getRootNames().includes(rootName)) { | ||
if (!this.model.document.getRoot(rootName)) { | ||
return false; | ||
@@ -396,0 +411,0 @@ } |
@@ -27,4 +27,6 @@ /** | ||
export { default as OperationFactory } from './model/operation/operationfactory'; | ||
export type { default as AttributeOperation } from './model/operation/attributeoperation'; | ||
export type { default as RenameOperation } from './model/operation/renameoperation'; | ||
export { default as AttributeOperation } from './model/operation/attributeoperation'; | ||
export { default as RenameOperation } from './model/operation/renameoperation'; | ||
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation'; | ||
export { default as RootOperation } from './model/operation/rootoperation'; | ||
export { transformSets } from './model/operation/transform'; | ||
@@ -31,0 +33,0 @@ export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent } from './model/documentselection'; |
@@ -21,2 +21,6 @@ /** | ||
export { default as OperationFactory } from './model/operation/operationfactory'; | ||
export { default as AttributeOperation } from './model/operation/attributeoperation'; | ||
export { default as RenameOperation } from './model/operation/renameoperation'; | ||
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation'; | ||
export { default as RootOperation } from './model/operation/rootoperation'; | ||
export { transformSets } from './model/operation/transform'; | ||
@@ -23,0 +27,0 @@ // Model. |
@@ -49,2 +49,8 @@ /** | ||
/** | ||
* A map that stores all roots that have been changed. | ||
* | ||
* The keys are the names of the roots while value represents the changes. | ||
*/ | ||
private readonly _changedRoots; | ||
/** | ||
* Stores the number of changes that were processed. Used to order the changes chronologically. It is important | ||
@@ -87,5 +93,2 @@ * when changes are sorted. | ||
* | ||
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ` | ||
* in the state before the operation is executed. | ||
* | ||
* @param operationToBuffer An operation to buffer. | ||
@@ -137,2 +140,3 @@ */ | ||
* * attribute changes, | ||
* * a root is added or detached, | ||
* * changes of markers which were defined as `affectsData`, | ||
@@ -164,2 +168,8 @@ * * changes of markers' `affectsData` property. | ||
/** | ||
* Returns all roots that have changed (either were attached, or detached, or their attributes changed). | ||
* | ||
* @returns Diff between the old and the new roots state. | ||
*/ | ||
getChangedRoots(): Array<DiffItemRoot>; | ||
/** | ||
* Returns a set of model items that were marked to get refreshed. | ||
@@ -173,2 +183,10 @@ */ | ||
/** | ||
* Buffers the root state change after the root was attached or detached | ||
*/ | ||
private _bufferRootStateChange; | ||
/** | ||
* Buffers a root attribute change. | ||
*/ | ||
private _bufferRootAttributeChange; | ||
/** | ||
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted | ||
@@ -261,3 +279,3 @@ * in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher. | ||
/** | ||
* The single diff item for inserted nodes. | ||
* A single diff item for inserted nodes. | ||
*/ | ||
@@ -282,3 +300,3 @@ export interface DiffItemInsert { | ||
/** | ||
* The length of an inserted text node. For elements it is always 1 as each inserted element is counted as a one. | ||
* The length of an inserted text node. For elements, it is always 1 as each inserted element is counted as a one. | ||
*/ | ||
@@ -288,3 +306,3 @@ length: number; | ||
/** | ||
* The single diff item for removed nodes. | ||
* A single diff item for removed nodes. | ||
*/ | ||
@@ -309,3 +327,3 @@ export interface DiffItemRemove { | ||
/** | ||
* The length of a removed text node. For elements it is always 1 as each removed element is counted as a one. | ||
* The length of a removed text node. For elements, it is always 1, as each removed element is counted as a one. | ||
*/ | ||
@@ -315,3 +333,3 @@ length: number; | ||
/** | ||
* The single diff item for attribute change. | ||
* A single diff item for attribute change. | ||
*/ | ||
@@ -340,1 +358,27 @@ export interface DiffItemAttribute { | ||
} | ||
/** | ||
* A single diff item for a changed root. | ||
*/ | ||
export interface DiffItemRoot { | ||
/** | ||
* Name of the changed root. | ||
*/ | ||
name: string; | ||
/** | ||
* Set accordingly if the root got attached or detached. Otherwise, not set. | ||
*/ | ||
state?: 'attached' | 'detached'; | ||
/** | ||
* Keeps all attribute changes that happened on the root. | ||
* | ||
* The keys are keys of the changed attributes. The values are objects containing the attribute value before the change | ||
* (`oldValue`) and after the change (`newValue`). | ||
* | ||
* Note, that if the root state changed (`state` is set), then `attributes` property will not be set. All attributes should be | ||
* handled together with the root being attached or detached. | ||
*/ | ||
attributes?: Record<string, { | ||
oldValue: unknown; | ||
newValue: unknown; | ||
}>; | ||
} |
@@ -48,2 +48,8 @@ /** | ||
/** | ||
* A map that stores all roots that have been changed. | ||
* | ||
* The keys are the names of the roots while value represents the changes. | ||
*/ | ||
this._changedRoots = new Map(); | ||
/** | ||
* Stores the number of changes that were processed. Used to order the changes chronologically. It is important | ||
@@ -79,3 +85,3 @@ * when changes are sorted. | ||
get isEmpty() { | ||
return this._changesInElement.size == 0 && this._changedMarkers.size == 0; | ||
return this._changesInElement.size == 0 && this._changedMarkers.size == 0 && this._changedRoots.size == 0; | ||
} | ||
@@ -85,5 +91,2 @@ /** | ||
* | ||
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ` | ||
* in the state before the operation is executed. | ||
* | ||
* @param operationToBuffer An operation to buffer. | ||
@@ -180,2 +183,14 @@ */ | ||
} | ||
case 'detachRoot': | ||
case 'addRoot': { | ||
this._bufferRootStateChange(operation.rootName, operation.isAdd); | ||
break; | ||
} | ||
case 'addRootAttribute': | ||
case 'removeRootAttribute': | ||
case 'changeRootAttribute': { | ||
const rootName = operation.root.rootName; | ||
this._bufferRootAttributeChange(rootName, operation.key, operation.oldValue, operation.newValue); | ||
break; | ||
} | ||
} | ||
@@ -256,2 +271,3 @@ // Clear cache after each buffered operation as it is no longer valid. | ||
* * attribute changes, | ||
* * a root is added or detached, | ||
* * changes of markers which were defined as `affectsData`, | ||
@@ -264,2 +280,5 @@ * * changes of markers' `affectsData` property. | ||
} | ||
if (this._changedRoots.size > 0) { | ||
return true; | ||
} | ||
for (const { newMarkerData, oldMarkerData } of this._changedMarkers.values()) { | ||
@@ -439,2 +458,23 @@ if (newMarkerData.affectsData !== oldMarkerData.affectsData) { | ||
/** | ||
* Returns all roots that have changed (either were attached, or detached, or their attributes changed). | ||
* | ||
* @returns Diff between the old and the new roots state. | ||
*/ | ||
getChangedRoots() { | ||
return Array.from(this._changedRoots.values()).map(diffItem => { | ||
const entry = { ...diffItem }; | ||
if (entry.state !== undefined) { | ||
// The root was attached or detached -- do not return its attributes changes. | ||
// If the root was attached, it should be handled as a whole, together with its attributes, the same way as model nodes. | ||
// If the root was detached, its attributes should be discarded anyway. | ||
// | ||
// Keep in mind that filtering must happen on this stage (when retrieving changes). If filtering happens on-the-fly as | ||
// the attributes change, it may lead to incorrect situation, e.g.: detach root, change attribute, re-attach root. | ||
// In this case, attribute change cannot be filtered. After the root is re-attached, the attribute change must be kept. | ||
delete entry.attributes; | ||
} | ||
return entry; | ||
}); | ||
} | ||
/** | ||
* Returns a set of model items that were marked to get refreshed. | ||
@@ -452,2 +492,3 @@ */ | ||
this._changedMarkers.clear(); | ||
this._changedRoots.clear(); | ||
this._refreshedItems = new Set(); | ||
@@ -457,2 +498,61 @@ this._cachedChanges = null; | ||
/** | ||
* Buffers the root state change after the root was attached or detached | ||
*/ | ||
_bufferRootStateChange(rootName, isAttached) { | ||
if (!this._changedRoots.has(rootName)) { | ||
this._changedRoots.set(rootName, { name: rootName, state: isAttached ? 'attached' : 'detached' }); | ||
return; | ||
} | ||
const diffItem = this._changedRoots.get(rootName); | ||
if (diffItem.state !== undefined) { | ||
// Root `state` can only toggle between of the values ('attached' or 'detached') and no value. It cannot be any other way, | ||
// because if the root was originally attached it can only become detached. Then, if it is re-attached in the same batch of | ||
// changes, it gets back to "no change" (which means no value). Same if the root was originally detached. | ||
delete diffItem.state; | ||
if (diffItem.attributes === undefined) { | ||
// If there is no `state` change and no `attributes` change, remove the entry. | ||
this._changedRoots.delete(rootName); | ||
} | ||
} | ||
else { | ||
diffItem.state = isAttached ? 'attached' : 'detached'; | ||
} | ||
} | ||
/** | ||
* Buffers a root attribute change. | ||
*/ | ||
_bufferRootAttributeChange(rootName, key, oldValue, newValue) { | ||
const diffItem = this._changedRoots.get(rootName) || { name: rootName }; | ||
const attrs = diffItem.attributes || {}; | ||
if (attrs[key]) { | ||
// If this attribute or metadata was already changed earlier and is changed again, check to what value it is changed. | ||
const attrEntry = attrs[key]; | ||
if (newValue === attrEntry.oldValue) { | ||
// If it was changed back to the old value, remove the entry. | ||
delete attrs[key]; | ||
} | ||
else { | ||
// If it was changed to a different value, update the entry. | ||
attrEntry.newValue = newValue; | ||
} | ||
} | ||
else { | ||
// If this attribute or metadata was not set earlier, add an entry. | ||
attrs[key] = { oldValue, newValue }; | ||
} | ||
if (Object.entries(attrs).length === 0) { | ||
// If attributes or metadata changes set became empty, remove it from the diff item. | ||
delete diffItem.attributes; | ||
if (diffItem.state === undefined) { | ||
// If there is no `state` change and no `attributes` change, remove the entry. | ||
this._changedRoots.delete(rootName); | ||
} | ||
} | ||
else { | ||
// Make sure that, if a new object in the structure was created, it gets set. | ||
diffItem.attributes = attrs; | ||
this._changedRoots.set(rootName, diffItem); | ||
} | ||
} | ||
/** | ||
* Marks the given `item` in differ to be "refreshed". It means that the item will be marked as removed and inserted | ||
@@ -459,0 +559,0 @@ * in the differ changes set, so it will be effectively re-converted when the differ changes are handled by a dispatcher. |
@@ -51,4 +51,4 @@ /** | ||
/** | ||
* A list of roots that are owned and managed by this document. Use {@link #createRoot} and | ||
* {@link #getRoot} to manipulate it. | ||
* A list of roots that are owned and managed by this document. Use {@link #createRoot}, {@link #getRoot} and | ||
* {@link #getRootNames} to manipulate it. | ||
*/ | ||
@@ -65,3 +65,3 @@ readonly roots: Collection<RootElement>; | ||
/** | ||
* A boolean indicates whether the selection has changed until | ||
* A flag that indicates whether the selection has changed since last change block. | ||
*/ | ||
@@ -92,4 +92,7 @@ private _hasSelectionChangedFromTheLastChangeBlock; | ||
* | ||
* **Note:** do not use this method after the editor has been initialized! If you want to dynamically add a root, use | ||
* {@link module:engine/model/writer~Writer#addRoot `model.Writer#addRoot`} instead. | ||
* | ||
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined | ||
* (`$block`s are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* @param rootName A unique root name. | ||
@@ -106,3 +109,6 @@ * @returns The created root. | ||
* | ||
* @param name A unique root name. | ||
* Detached roots are returned by this method. This is to be able to operate on the detached root (for example, to be able to create | ||
* a position inside such a root for undo feature purposes). | ||
* | ||
* @param name The root name of the root to return. | ||
* @returns The root registered under a given name or `null` when there is no root with the given name. | ||
@@ -112,7 +118,11 @@ */ | ||
/** | ||
* Returns an array with names of all roots (without the {@link #graveyard}) added to the document. | ||
* Returns an array with names of all roots added to the document (except the {@link #graveyard graveyard root}). | ||
* | ||
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate | ||
* on the document data know which roots are still a part of the document and should be processed. | ||
* | ||
* @param includeDetached Specified whether detached roots should be returned as well. | ||
* @returns Roots names. | ||
*/ | ||
getRootNames(): Array<string>; | ||
getRootNames(includeDetached?: boolean): Array<string>; | ||
/** | ||
@@ -119,0 +129,0 @@ * Used to register a post-fixer callback. A post-fixer mechanism guarantees that the features |
@@ -82,2 +82,29 @@ /** | ||
}); | ||
// This is a solution for a problem that may occur during real-time editing. If one client detached a root and another added | ||
// something there at the same moment, the OT does not solve this problem currently. In such situation, the added elements would | ||
// stay in the detached root. | ||
// | ||
// This is incorrect, a detached root should be empty and all elements from it should be removed. To solve this, the post-fixer will | ||
// remove any element that is left in a detached root. | ||
// | ||
// Similarly, markers that are created at the beginning or at the end of the detached root will not be removed as well. | ||
// | ||
// The drawback of this solution over the OT solution is that the elements removed by the post-fixer will never be brought back. | ||
// If the root detachment gets undone (and the root is brought back), the removed elements will not be there. | ||
this.registerPostFixer(writer => { | ||
let result = false; | ||
for (const root of this.roots) { | ||
if (!root.isAttached() && !root.isEmpty) { | ||
writer.remove(writer.createRangeIn(root)); | ||
result = true; | ||
} | ||
} | ||
for (const marker of this.model.markers) { | ||
if (!marker.getRange().root.isAttached()) { | ||
writer.removeMarker(marker); | ||
result = true; | ||
} | ||
} | ||
return result; | ||
}); | ||
} | ||
@@ -108,4 +135,7 @@ /** | ||
* | ||
* **Note:** do not use this method after the editor has been initialized! If you want to dynamically add a root, use | ||
* {@link module:engine/model/writer~Writer#addRoot `model.Writer#addRoot`} instead. | ||
* | ||
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined | ||
* (`$block`s are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* @param rootName A unique root name. | ||
@@ -137,3 +167,6 @@ * @returns The created root. | ||
* | ||
* @param name A unique root name. | ||
* Detached roots are returned by this method. This is to be able to operate on the detached root (for example, to be able to create | ||
* a position inside such a root for undo feature purposes). | ||
* | ||
* @param name The root name of the root to return. | ||
* @returns The root registered under a given name or `null` when there is no root with the given name. | ||
@@ -145,8 +178,14 @@ */ | ||
/** | ||
* Returns an array with names of all roots (without the {@link #graveyard}) added to the document. | ||
* Returns an array with names of all roots added to the document (except the {@link #graveyard graveyard root}). | ||
* | ||
* Detached roots **are not** returned by this method by default. This is to make sure that all features or algorithms that operate | ||
* on the document data know which roots are still a part of the document and should be processed. | ||
* | ||
* @param includeDetached Specified whether detached roots should be returned as well. | ||
* @returns Roots names. | ||
*/ | ||
getRootNames() { | ||
return Array.from(this.roots, root => root.rootName).filter(name => name != graveyardName); | ||
getRootNames(includeDetached = false) { | ||
return Array.from(this.roots) | ||
.filter(root => root.rootName != graveyardName && (includeDetached || root.isAttached())) | ||
.map(root => root.rootName); | ||
} | ||
@@ -153,0 +192,0 @@ /** |
@@ -86,2 +86,6 @@ /** | ||
/** | ||
* Returns `false` as `DocumentFragment` by definition is not attached to a document. Added for compatibility reasons. | ||
*/ | ||
isAttached(): false; | ||
/** | ||
* Returns empty array. Added for compatibility reasons. | ||
@@ -88,0 +92,0 @@ */ |
@@ -104,2 +104,8 @@ /** | ||
/** | ||
* Returns `false` as `DocumentFragment` by definition is not attached to a document. Added for compatibility reasons. | ||
*/ | ||
isAttached() { | ||
return false; | ||
} | ||
/** | ||
* Returns empty array. Added for compatibility reasons. | ||
@@ -106,0 +112,0 @@ */ |
@@ -69,3 +69,3 @@ /** | ||
/** | ||
* Index of this node in it's parent or `null` if the node has no parent. | ||
* Index of this node in its parent or `null` if the node has no parent. | ||
* | ||
@@ -77,4 +77,4 @@ * Accessing this property throws an error if this node's parent element does not contain it. | ||
/** | ||
* Offset at which this node starts in it's parent. It is equal to the sum of {@link #offsetSize offsetSize} | ||
* of all it's previous siblings. Equals to `null` if node has no parent. | ||
* Offset at which this node starts in its parent. It is equal to the sum of {@link #offsetSize offsetSize} | ||
* of all its previous siblings. Equals to `null` if node has no parent. | ||
* | ||
@@ -112,3 +112,3 @@ * Accessing this property throws an error if this node's parent element does not contain it. | ||
/** | ||
* Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots). | ||
* Returns `true` if the node is inside a document root that is attached to the document. | ||
*/ | ||
@@ -115,0 +115,0 @@ isAttached(): boolean; |
@@ -66,3 +66,3 @@ /** | ||
/** | ||
* Index of this node in it's parent or `null` if the node has no parent. | ||
* Index of this node in its parent or `null` if the node has no parent. | ||
* | ||
@@ -83,4 +83,4 @@ * Accessing this property throws an error if this node's parent element does not contain it. | ||
/** | ||
* Offset at which this node starts in it's parent. It is equal to the sum of {@link #offsetSize offsetSize} | ||
* of all it's previous siblings. Equals to `null` if node has no parent. | ||
* Offset at which this node starts in its parent. It is equal to the sum of {@link #offsetSize offsetSize} | ||
* of all its previous siblings. Equals to `null` if node has no parent. | ||
* | ||
@@ -147,6 +147,10 @@ * Accessing this property throws an error if this node's parent element does not contain it. | ||
/** | ||
* Returns true if the node is in a tree rooted in the document (is a descendant of one of its roots). | ||
* Returns `true` if the node is inside a document root that is attached to the document. | ||
*/ | ||
isAttached() { | ||
return this.root.is('rootElement'); | ||
// If the node has no parent it means that it is a root. | ||
// But this is not a `RootElement`, so it means that it is not attached. | ||
// | ||
// If this is not the root, check if this element's root is attached. | ||
return this.parent === null ? false : this.root.isAttached(); | ||
} | ||
@@ -153,0 +157,0 @@ /** |
@@ -92,3 +92,3 @@ /** | ||
/** | ||
* Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `AttributeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -95,0 +95,0 @@ * @param json Deserialized JSON object. |
@@ -134,3 +134,3 @@ /** | ||
/** | ||
* Creates `AttributeOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `AttributeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -137,0 +137,0 @@ * @param json Deserialized JSON object. |
@@ -79,3 +79,3 @@ /** | ||
/** | ||
* Creates `InsertOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `InsertOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -82,0 +82,0 @@ * @param json Deserialized JSON object. |
@@ -108,3 +108,3 @@ /** | ||
/** | ||
* Creates `InsertOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `InsertOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -111,0 +111,0 @@ * @param json Deserialized JSON object. |
@@ -89,3 +89,3 @@ /** | ||
/** | ||
* Creates `MergeOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `MergeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -92,0 +92,0 @@ * @param json Deserialized JSON object. |
@@ -144,3 +144,3 @@ /** | ||
/** | ||
* Creates `MergeOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `MergeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -147,0 +147,0 @@ * @param json Deserialized JSON object. |
@@ -85,3 +85,3 @@ /** | ||
/** | ||
* Creates `MoveOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `MoveOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -88,0 +88,0 @@ * @param json Deserialized JSON object. |
@@ -145,3 +145,3 @@ /** | ||
/** | ||
* Creates `MoveOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `MoveOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -148,0 +148,0 @@ * @param json Deserialized JSON object. |
@@ -83,3 +83,3 @@ /** | ||
/** | ||
* Creates Operation object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `Operation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -86,0 +86,0 @@ * @param json Deserialized JSON object. |
@@ -54,3 +54,3 @@ /** | ||
/** | ||
* Creates Operation object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `Operation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -57,0 +57,0 @@ * @param json Deserialized JSON object. |
@@ -16,2 +16,3 @@ /** | ||
import RootAttributeOperation from './rootattributeoperation'; | ||
import RootOperation from './rootoperation'; | ||
import SplitOperation from './splitoperation'; | ||
@@ -28,2 +29,3 @@ import MergeOperation from './mergeoperation'; | ||
operations[RootAttributeOperation.className] = RootAttributeOperation; | ||
operations[RootOperation.className] = RootOperation; | ||
operations[SplitOperation.className] = SplitOperation; | ||
@@ -30,0 +32,0 @@ operations[MergeOperation.className] = MergeOperation; |
@@ -24,14 +24,10 @@ /** | ||
* Root element to change. | ||
* | ||
* @readonly | ||
*/ | ||
root: RootElement; | ||
readonly root: RootElement; | ||
/** | ||
* Key of an attribute to change or remove. | ||
* | ||
* @readonly | ||
*/ | ||
key: string; | ||
readonly key: string; | ||
/** | ||
* Old value of the attribute with given key or `null` if adding a new attribute. | ||
* Old value of the attribute with given key or `null`, if attribute was not set before. | ||
* | ||
@@ -42,3 +38,3 @@ * @readonly | ||
/** | ||
* New value to set for the attribute. If `null`, then the operation just removes the attribute. | ||
* New value of the attribute with given key or `null`, if operation should remove attribute. | ||
* | ||
@@ -54,4 +50,4 @@ * @readonly | ||
* @param key Key of an attribute to change or remove. | ||
* @param oldValue Old value of the attribute with given key or `null` if adding a new attribute. | ||
* @param newValue New value to set for the attribute. If `null`, then the operation just removes the attribute. | ||
* @param oldValue Old value of the attribute with given key or `null`, if attribute was not set before. | ||
* @param newValue New value of the attribute with given key or `null`, if operation should remove attribute. | ||
* @param baseVersion Document {@link module:engine/model/document~Document#version} on which operation | ||
@@ -94,3 +90,3 @@ * can be applied or `null` if the operation operates on detached (non-document) tree. | ||
/** | ||
* Creates RootAttributeOperation object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `RootAttributeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -97,0 +93,0 @@ * @param json Deserialized JSON object. |
@@ -27,4 +27,4 @@ /** | ||
* @param key Key of an attribute to change or remove. | ||
* @param oldValue Old value of the attribute with given key or `null` if adding a new attribute. | ||
* @param newValue New value to set for the attribute. If `null`, then the operation just removes the attribute. | ||
* @param oldValue Old value of the attribute with given key or `null`, if attribute was not set before. | ||
* @param newValue New value of the attribute with given key or `null`, if operation should remove attribute. | ||
* @param baseVersion Document {@link module:engine/model/document~Document#version} on which operation | ||
@@ -37,4 +37,4 @@ * can be applied or `null` if the operation operates on detached (non-document) tree. | ||
this.key = key; | ||
this.oldValue = oldValue; | ||
this.newValue = newValue; | ||
this.oldValue = oldValue === undefined ? null : oldValue; | ||
this.newValue = newValue === undefined ? null : newValue; | ||
} | ||
@@ -87,3 +87,3 @@ /** | ||
/** | ||
* The attribute which should be removed does not exists for the given node. | ||
* The attribute which should be removed does not exist for the given node. | ||
* | ||
@@ -135,3 +135,3 @@ * @error rootattribute-operation-wrong-old-value | ||
/** | ||
* Creates RootAttributeOperation object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `RootAttributeOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -138,0 +138,0 @@ * @param json Deserialized JSON object. |
@@ -98,3 +98,3 @@ /** | ||
/** | ||
* Creates `SplitOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `SplitOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -101,0 +101,0 @@ * @param json Deserialized JSON object. |
@@ -169,3 +169,3 @@ /** | ||
/** | ||
* Creates `SplitOperation` object from deserilized object, i.e. from parsed JSON string. | ||
* Creates `SplitOperation` object from deserialized object, i.e. from parsed JSON string. | ||
* | ||
@@ -172,0 +172,0 @@ * @param json Deserialized JSON object. |
@@ -23,2 +23,6 @@ /** | ||
/** | ||
* @internal | ||
*/ | ||
_isAttached: boolean; | ||
/** | ||
* Creates root element. | ||
@@ -36,4 +40,14 @@ * | ||
/** | ||
* Converts `RootElement` instance to `string` containing it's name. | ||
* Informs if the root element is currently attached to the document, or not. | ||
* | ||
* A detached root is equivalent to being removed and cannot contain any children or markers. | ||
* | ||
* By default, a newly added root is attached. It can be detached using | ||
* {@link module:engine/model/writer~Writer#detachRoot `Writer#detachRoot`}. A detached root can be re-attached again using | ||
* {@link module:engine/model/writer~Writer#addRoot `Writer#addRoot`}. | ||
*/ | ||
isAttached(): boolean; | ||
/** | ||
* Converts `RootElement` instance to `string` containing its name. | ||
* | ||
* @returns `RootElement` instance converted to `string`. | ||
@@ -40,0 +54,0 @@ */ |
@@ -22,2 +22,6 @@ /** | ||
super(name); | ||
/** | ||
* @internal | ||
*/ | ||
this._isAttached = true; | ||
this._document = document; | ||
@@ -33,4 +37,16 @@ this.rootName = rootName; | ||
/** | ||
* Converts `RootElement` instance to `string` containing it's name. | ||
* Informs if the root element is currently attached to the document, or not. | ||
* | ||
* A detached root is equivalent to being removed and cannot contain any children or markers. | ||
* | ||
* By default, a newly added root is attached. It can be detached using | ||
* {@link module:engine/model/writer~Writer#detachRoot `Writer#detachRoot`}. A detached root can be re-attached again using | ||
* {@link module:engine/model/writer~Writer#addRoot `Writer#addRoot`}. | ||
*/ | ||
isAttached() { | ||
return this._isAttached; | ||
} | ||
/** | ||
* Converts `RootElement` instance to `string` containing its name. | ||
* | ||
* @returns `RootElement` instance converted to `string`. | ||
@@ -37,0 +53,0 @@ */ |
@@ -9,2 +9,3 @@ /** | ||
import Range from './range'; | ||
import RootElement from './rootelement'; | ||
import Text from './text'; | ||
@@ -370,3 +371,3 @@ import type { Marker } from './markercollection'; | ||
* | ||
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}. | ||
* These parameters work the same way as {@link #createPositionAt `writer.createPositionAt()`}. | ||
* | ||
@@ -629,2 +630,29 @@ * Note that items can be moved only within the same tree. It means that you can move items within the same root | ||
/** | ||
* Adds a new root to the document (or re-attaches a {@link #detachRoot detached root}). | ||
* | ||
* Throws an error, if trying to add a root that is already added and attached. | ||
* | ||
* @param rootName Name of the added root. | ||
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined | ||
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* @returns The added root element. | ||
*/ | ||
addRoot(rootName: string, elementName?: string): RootElement; | ||
/** | ||
* Detaches the root from the document. | ||
* | ||
* All content and markers are removed from the root upon detaching. New content and new markers cannot be added to the root, as long | ||
* as it is detached. | ||
* | ||
* A root cannot be fully removed from the document, it can be only detached. A root is permanently removed only after you | ||
* re-initialize the editor and do not specify the root in the initial data. | ||
* | ||
* A detached root can be re-attached using {@link #addRoot}. | ||
* | ||
* Throws an error if the root does not exist or the root is already detached. | ||
* | ||
* @param rootOrName Name of the detached root. | ||
*/ | ||
detachRoot(rootOrName: string | RootElement): void; | ||
/** | ||
* Sets the document's selection (ranges and direction) to the specified location based on the given | ||
@@ -631,0 +659,0 @@ * {@link module:engine/model/selection~Selectable selectable} or creates an empty selection if no arguments were passed. |
@@ -16,2 +16,3 @@ /** | ||
import RootAttributeOperation from './operation/rootattributeoperation'; | ||
import RootOperation from './operation/rootoperation'; | ||
import SplitOperation from './operation/splitoperation'; | ||
@@ -365,3 +366,3 @@ import DocumentFragment from './documentfragment'; | ||
* | ||
* These parameters works the same way as {@link #createPositionAt `writer.createPositionAt()`}. | ||
* These parameters work the same way as {@link #createPositionAt `writer.createPositionAt()`}. | ||
* | ||
@@ -922,2 +923,74 @@ * Note that items can be moved only within the same tree. It means that you can move items within the same root | ||
} | ||
/** | ||
* Adds a new root to the document (or re-attaches a {@link #detachRoot detached root}). | ||
* | ||
* Throws an error, if trying to add a root that is already added and attached. | ||
* | ||
* @param rootName Name of the added root. | ||
* @param elementName The element name. Defaults to `'$root'` which also has some basic schema defined | ||
* (e.g. `$block` elements are allowed inside the `$root`). Make sure to define a proper schema if you use a different name. | ||
* @returns The added root element. | ||
*/ | ||
addRoot(rootName, elementName = '$root') { | ||
this._assertWriterUsedCorrectly(); | ||
const root = this.model.document.getRoot(rootName); | ||
if (root && root.isAttached()) { | ||
/** | ||
* Root with provided name already exists and is attached. | ||
* | ||
* @error writer-addroot-root-exists | ||
*/ | ||
throw new CKEditorError('writer-addroot-root-exists', this); | ||
} | ||
const document = this.model.document; | ||
const operation = new RootOperation(rootName, elementName, true, document, document.version); | ||
this.batch.addOperation(operation); | ||
this.model.applyOperation(operation); | ||
return this.model.document.getRoot(rootName); | ||
} | ||
/** | ||
* Detaches the root from the document. | ||
* | ||
* All content and markers are removed from the root upon detaching. New content and new markers cannot be added to the root, as long | ||
* as it is detached. | ||
* | ||
* A root cannot be fully removed from the document, it can be only detached. A root is permanently removed only after you | ||
* re-initialize the editor and do not specify the root in the initial data. | ||
* | ||
* A detached root can be re-attached using {@link #addRoot}. | ||
* | ||
* Throws an error if the root does not exist or the root is already detached. | ||
* | ||
* @param rootOrName Name of the detached root. | ||
*/ | ||
detachRoot(rootOrName) { | ||
this._assertWriterUsedCorrectly(); | ||
const root = typeof rootOrName == 'string' ? this.model.document.getRoot(rootOrName) : rootOrName; | ||
if (!root || !root.isAttached()) { | ||
/** | ||
* Root with provided name does not exist or is already detached. | ||
* | ||
* @error writer-detachroot-no-root | ||
*/ | ||
throw new CKEditorError('writer-detachroot-no-root', this); | ||
} | ||
// First, remove all markers from the root. It is better to do it before removing stuff for undo purposes. | ||
// However, looking through all the markers may not be the best performance wise. But there's no better solution for now. | ||
for (const marker of this.model.markers) { | ||
if (marker.getRange().root === root) { | ||
this.removeMarker(marker); | ||
} | ||
} | ||
// Remove all attributes from the root. | ||
for (const key of root.getAttributeKeys()) { | ||
this.removeAttribute(key, root); | ||
} | ||
// Remove all contents of the root. | ||
this.remove(this.createRangeIn(root)); | ||
// Finally, detach the root. | ||
const document = this.model.document; | ||
const operation = new RootOperation(root.rootName, root.name, false, document, document.version); | ||
this.batch.addOperation(operation); | ||
this.model.applyOperation(operation); | ||
} | ||
setSelection(...args) { | ||
@@ -924,0 +997,0 @@ this._assertWriterUsedCorrectly(); |
@@ -450,3 +450,3 @@ /** | ||
* | ||
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* Refer to the {@glink updating/guides/update-to-29##update-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* and {@link module:engine/view/matcher~MatcherPattern} documentation. | ||
@@ -482,3 +482,3 @@ * | ||
* | ||
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* Refer to the {@glink updating/guides/update-to-29##update-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* and the {@link module:engine/view/matcher~MatcherPattern} documentation. | ||
@@ -485,0 +485,0 @@ * |
@@ -471,3 +471,3 @@ /** | ||
* | ||
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* Refer to the {@glink updating/guides/update-to-29##update-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* and {@link module:engine/view/matcher~MatcherPattern} documentation. | ||
@@ -503,3 +503,3 @@ * | ||
* | ||
* Refer to the {@glink updating/migration-to-29##migration-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* Refer to the {@glink updating/guides/update-to-29##update-to-ckeditor-5-v2910 Migration to v29.1.0} guide | ||
* and the {@link module:engine/view/matcher~MatcherPattern} documentation. | ||
@@ -506,0 +506,0 @@ * |
@@ -26,2 +26,6 @@ /** | ||
observe(): void; | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving(): void; | ||
} | ||
@@ -28,0 +32,0 @@ /** |
@@ -36,2 +36,6 @@ /** | ||
observe() { } | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving() { } | ||
} |
@@ -60,2 +60,6 @@ /** | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving(domElement: HTMLElement): void; | ||
/** | ||
* Calls `Document#fire()` if observer {@link #isEnabled is enabled}. | ||
@@ -62,0 +66,0 @@ * |
@@ -60,2 +60,8 @@ /** | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving(domElement) { | ||
this.stopListening(domElement); | ||
} | ||
/** | ||
* Calls `Document#fire()` if observer {@link #isEnabled is enabled}. | ||
@@ -62,0 +68,0 @@ * |
@@ -33,2 +33,6 @@ /** | ||
*/ | ||
stopObserving(): void; | ||
/** | ||
* @inheritDoc | ||
*/ | ||
destroy(): void; | ||
@@ -35,0 +39,0 @@ /** |
@@ -51,2 +51,6 @@ /** | ||
*/ | ||
stopObserving() { } | ||
/** | ||
* @inheritDoc | ||
*/ | ||
destroy() { | ||
@@ -53,0 +57,0 @@ super.destroy(); |
@@ -59,2 +59,6 @@ /** | ||
*/ | ||
stopObserving(domElement: HTMLElement): void; | ||
/** | ||
* @inheritDoc | ||
*/ | ||
enable(): void; | ||
@@ -61,0 +65,0 @@ /** |
@@ -36,3 +36,3 @@ /** | ||
this.renderer = view._renderer; | ||
this._domElements = []; | ||
this._domElements = new Set(); | ||
this._mutationObserver = new window.MutationObserver(this._onMutations.bind(this)); | ||
@@ -50,3 +50,3 @@ } | ||
observe(domElement) { | ||
this._domElements.push(domElement); | ||
this._domElements.add(domElement); | ||
if (this.isEnabled) { | ||
@@ -59,2 +59,16 @@ this._mutationObserver.observe(domElement, this._config); | ||
*/ | ||
stopObserving(domElement) { | ||
this._domElements.delete(domElement); | ||
if (this.isEnabled) { | ||
// Unfortunately, it is not possible to stop observing particular DOM element. | ||
// In order to stop observing one of multiple DOM elements, we need to re-connect the mutation observer. | ||
this._mutationObserver.disconnect(); | ||
for (const domElement of this._domElements) { | ||
this._mutationObserver.observe(domElement, this._config); | ||
} | ||
} | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
enable() { | ||
@@ -61,0 +75,0 @@ super.enable(); |
@@ -74,7 +74,12 @@ /** | ||
/** | ||
* Starts observing the given root element. | ||
* Starts observing given DOM element. | ||
* | ||
* @param name The name of the root element. | ||
* @param domElement DOM element to observe. | ||
* @param name The name of the related root element. | ||
*/ | ||
abstract observe(domElement: HTMLElement, name: string): void; | ||
/** | ||
* Stops observing given DOM element. | ||
*/ | ||
abstract stopObserving(domElement: HTMLElement): void; | ||
} | ||
@@ -81,0 +86,0 @@ /** |
@@ -80,2 +80,6 @@ /** | ||
*/ | ||
stopObserving(domElement: HTMLElement): void; | ||
/** | ||
* @inheritDoc | ||
*/ | ||
destroy(): void; | ||
@@ -82,0 +86,0 @@ private _reportInfiniteLoop; |
@@ -107,2 +107,8 @@ /** | ||
*/ | ||
stopObserving(domElement) { | ||
this.stopListening(domElement); | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
destroy() { | ||
@@ -109,0 +115,0 @@ super.destroy(); |
@@ -27,2 +27,6 @@ /** | ||
observe(): void; | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving(): void; | ||
} | ||
@@ -29,0 +33,0 @@ /** |
@@ -38,2 +38,6 @@ /** | ||
observe() { } | ||
/** | ||
* @inheritDoc | ||
*/ | ||
stopObserving() { } | ||
} |
@@ -59,6 +59,6 @@ /** | ||
const doc = element.document; | ||
if (!documentPlaceholders.has(doc)) { | ||
return; | ||
} | ||
view.change(writer => { | ||
if (!documentPlaceholders.has(doc)) { | ||
return; | ||
} | ||
const placeholders = documentPlaceholders.get(doc); | ||
@@ -65,0 +65,0 @@ const config = placeholders.get(element); |
@@ -211,7 +211,7 @@ /** | ||
* @param expectedDom Expected DOM children. | ||
* @param options Options | ||
* @param options.replaceText Mark text nodes replacement. | ||
* @returns Actions array modified with the `replace` actions. | ||
* @param comparator A comparator function that should return `true` if the given node should be reused | ||
* (either by the update of a text node data or an element children list for similar elements). | ||
* @returns Actions array modified with the `update` actions. | ||
*/ | ||
private _findReplaceActions; | ||
private _findUpdateActions; | ||
/** | ||
@@ -218,0 +218,0 @@ * Marks text nodes to be synchronized. |
@@ -264,7 +264,7 @@ /** | ||
const diff = this._diffNodeLists(actualDomChildren, expectedDomChildren); | ||
const actions = this._findReplaceActions(diff, actualDomChildren, expectedDomChildren); | ||
if (actions.indexOf('replace') !== -1) { | ||
const actions = this._findUpdateActions(diff, actualDomChildren, expectedDomChildren, areSimilarElements); | ||
if (actions.indexOf('update') !== -1) { | ||
const counter = { equal: 0, insert: 0, delete: 0 }; | ||
for (const action of actions) { | ||
if (action === 'replace') { | ||
if (action === 'update') { | ||
const insertIndex = counter.equal + counter.insert; | ||
@@ -516,13 +516,5 @@ const deleteIndex = counter.equal + counter.delete; | ||
const diff = this._diffNodeLists(actualDomChildren, expectedDomChildren); | ||
// The rendering is not disabled on Android in the composition mode. | ||
// Composition events are not cancellable and browser will modify the DOM tree. | ||
// On Android composition events are immediately applied to the model, so we don't need to skip rendering, | ||
// and we should not do it because the difference between view and DOM could lead to position mapping problems. | ||
// Since the composition is fragile and often breaks if the composed text node is replaced while composing | ||
// we need to make sure that we update the existing text node and not replace it with another one. | ||
// We don't want to change the behavior on other browsers for safety, but maybe one day cause it seems to make sense. | ||
// https://github.com/ckeditor/ckeditor5/issues/12455. | ||
const actions = env.isAndroid ? | ||
this._findReplaceActions(diff, actualDomChildren, expectedDomChildren, { replaceText: true }) : | ||
diff; | ||
// We need to make sure that we update the existing text node and not replace it with another one. | ||
// The composition and different "language" browser extensions are fragile to text node being completely replaced. | ||
const actions = this._findUpdateActions(diff, actualDomChildren, expectedDomChildren, areTextNodes); | ||
let i = 0; | ||
@@ -546,3 +538,3 @@ const nodesToUnbind = new Set(); | ||
} | ||
else if (action === 'equal' || action === 'replace') { | ||
else if (action === 'equal' || action === 'update') { | ||
i++; | ||
@@ -563,3 +555,3 @@ } | ||
// Update the existing text node data. Note that replace action is generated only for Android for now. | ||
else if (action === 'replace') { | ||
else if (action === 'update') { | ||
// @if CK_DEBUG_TYPING // if ( ( window as any ).logCKETyping ) { | ||
@@ -620,7 +612,7 @@ // @if CK_DEBUG_TYPING // console.group( '%c[Renderer]%c Update text node', | ||
* @param expectedDom Expected DOM children. | ||
* @param options Options | ||
* @param options.replaceText Mark text nodes replacement. | ||
* @returns Actions array modified with the `replace` actions. | ||
* @param comparator A comparator function that should return `true` if the given node should be reused | ||
* (either by the update of a text node data or an element children list for similar elements). | ||
* @returns Actions array modified with the `update` actions. | ||
*/ | ||
_findReplaceActions(actions, actualDom, expectedDom, options = {}) { | ||
_findUpdateActions(actions, actualDom, expectedDom, comparator) { | ||
// If there is no both 'insert' and 'delete' actions, no need to check for replaced elements. | ||
@@ -642,4 +634,4 @@ if (actions.indexOf('insert') === -1 || actions.indexOf('delete') === -1) { | ||
else { // equal | ||
newActions = newActions.concat(diff(actualSlice, expectedSlice, options.replaceText ? areTextNodes : areSimilar) | ||
.map(x => x === 'equal' ? 'replace' : x)); | ||
newActions = newActions.concat(diff(actualSlice, expectedSlice, comparator) | ||
.map(action => action === 'equal' ? 'update' : action)); | ||
newActions.push('equal'); | ||
@@ -652,4 +644,4 @@ // Reset stored elements on 'equal'. | ||
} | ||
return newActions.concat(diff(actualSlice, expectedSlice, options.replaceText ? areTextNodes : areSimilar) | ||
.map(x => x === 'equal' ? 'replace' : x)); | ||
return newActions.concat(diff(actualSlice, expectedSlice, comparator) | ||
.map(action => action === 'equal' ? 'update' : action)); | ||
} | ||
@@ -889,3 +881,3 @@ /** | ||
*/ | ||
function areSimilar(node1, node2) { | ||
function areSimilarElements(node1, node2) { | ||
return isNode(node1) && isNode(node2) && | ||
@@ -892,0 +884,0 @@ !isText(node1) && !isText(node2) && |
@@ -218,2 +218,5 @@ /** | ||
this.domConverter.unbindDomElement(domRoot); | ||
for (const observer of this._observers.values()) { | ||
observer.stopObserving(domRoot); | ||
} | ||
} | ||
@@ -220,0 +223,0 @@ /** |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2809112
246
62304