@ckeditor/ckeditor5-engine 37.0.1 → 38.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -22
- package/src/controller/editingcontroller.js +27 -1
- package/src/conversion/conversion.d.ts +5 -3
- package/src/conversion/downcasthelpers.js +1 -1
- package/src/index.d.ts +3 -1
- package/src/index.js +2 -0
- package/src/model/document.d.ts +8 -0
- package/src/model/document.js +1 -0
- package/src/model/markercollection.d.ts +3 -3
- package/src/model/markercollection.js +3 -3
- package/src/model/model.d.ts +38 -3
- package/src/model/model.js +37 -3
- package/src/model/operation/attributeoperation.d.ts +5 -0
- package/src/model/operation/attributeoperation.js +6 -0
- package/src/model/operation/detachoperation.d.ts +5 -0
- package/src/model/operation/detachoperation.js +6 -0
- package/src/model/operation/insertoperation.d.ts +5 -0
- package/src/model/operation/insertoperation.js +6 -0
- package/src/model/operation/markeroperation.d.ts +5 -0
- package/src/model/operation/markeroperation.js +18 -0
- package/src/model/operation/mergeoperation.d.ts +5 -0
- package/src/model/operation/mergeoperation.js +12 -0
- package/src/model/operation/moveoperation.d.ts +5 -0
- package/src/model/operation/moveoperation.js +9 -0
- package/src/model/operation/nooperation.d.ts +5 -0
- package/src/model/operation/nooperation.js +6 -0
- package/src/model/operation/operation.d.ts +7 -0
- package/src/model/operation/renameoperation.d.ts +5 -0
- package/src/model/operation/renameoperation.js +6 -0
- package/src/model/operation/rootattributeoperation.d.ts +5 -0
- package/src/model/operation/rootattributeoperation.js +6 -0
- package/src/model/operation/rootoperation.d.ts +5 -0
- package/src/model/operation/rootoperation.js +6 -0
- package/src/model/operation/splitoperation.d.ts +5 -0
- package/src/model/operation/splitoperation.js +14 -0
- package/src/model/selection.d.ts +13 -3
- package/src/model/selection.js +71 -6
- package/src/model/typecheckable.js +1 -1
- package/src/model/utils/insertcontent.js +4 -4
- package/src/model/utils/selection-post-fixer.d.ts +9 -0
- package/src/model/utils/selection-post-fixer.js +3 -1
- package/src/model/writer.d.ts +1 -1
- package/src/view/datatransfer.d.ts +4 -0
- package/src/view/datatransfer.js +6 -0
- package/src/view/domconverter.js +12 -2
- package/src/view/matcher.js +1 -1
- package/src/view/observer/bubblingemittermixin.js +1 -1
- package/src/view/observer/selectionobserver.js +1 -1
- package/src/view/position.js +1 -4
- package/src/view/typecheckable.js +1 -1
- package/src/view/view.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "38.0.0-rc.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -23,30 +23,30 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "src/index.js",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@ckeditor/ckeditor5-utils": "^
|
|
26
|
+
"@ckeditor/ckeditor5-utils": "^38.0.0-rc.0",
|
|
27
27
|
"lodash-es": "^4.17.15"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ckeditor/ckeditor5-basic-styles": "^
|
|
31
|
-
"@ckeditor/ckeditor5-block-quote": "^
|
|
32
|
-
"@ckeditor/ckeditor5-clipboard": "^
|
|
33
|
-
"@ckeditor/ckeditor5-cloud-services": "^
|
|
34
|
-
"@ckeditor/ckeditor5-core": "^
|
|
35
|
-
"@ckeditor/ckeditor5-editor-classic": "^
|
|
36
|
-
"@ckeditor/ckeditor5-enter": "^
|
|
37
|
-
"@ckeditor/ckeditor5-essentials": "^
|
|
38
|
-
"@ckeditor/ckeditor5-heading": "^
|
|
39
|
-
"@ckeditor/ckeditor5-image": "^
|
|
40
|
-
"@ckeditor/ckeditor5-link": "^
|
|
41
|
-
"@ckeditor/ckeditor5-list": "^
|
|
42
|
-
"@ckeditor/ckeditor5-mention": "^
|
|
43
|
-
"@ckeditor/ckeditor5-paragraph": "^
|
|
44
|
-
"@ckeditor/ckeditor5-table": "^
|
|
45
|
-
"@ckeditor/ckeditor5-theme-lark": "^
|
|
46
|
-
"@ckeditor/ckeditor5-typing": "^
|
|
47
|
-
"@ckeditor/ckeditor5-ui": "^
|
|
48
|
-
"@ckeditor/ckeditor5-undo": "^
|
|
49
|
-
"@ckeditor/ckeditor5-widget": "^
|
|
30
|
+
"@ckeditor/ckeditor5-basic-styles": "^38.0.0-rc.0",
|
|
31
|
+
"@ckeditor/ckeditor5-block-quote": "^38.0.0-rc.0",
|
|
32
|
+
"@ckeditor/ckeditor5-clipboard": "^38.0.0-rc.0",
|
|
33
|
+
"@ckeditor/ckeditor5-cloud-services": "^38.0.0-rc.0",
|
|
34
|
+
"@ckeditor/ckeditor5-core": "^38.0.0-rc.0",
|
|
35
|
+
"@ckeditor/ckeditor5-editor-classic": "^38.0.0-rc.0",
|
|
36
|
+
"@ckeditor/ckeditor5-enter": "^38.0.0-rc.0",
|
|
37
|
+
"@ckeditor/ckeditor5-essentials": "^38.0.0-rc.0",
|
|
38
|
+
"@ckeditor/ckeditor5-heading": "^38.0.0-rc.0",
|
|
39
|
+
"@ckeditor/ckeditor5-image": "^38.0.0-rc.0",
|
|
40
|
+
"@ckeditor/ckeditor5-link": "^38.0.0-rc.0",
|
|
41
|
+
"@ckeditor/ckeditor5-list": "^38.0.0-rc.0",
|
|
42
|
+
"@ckeditor/ckeditor5-mention": "^38.0.0-rc.0",
|
|
43
|
+
"@ckeditor/ckeditor5-paragraph": "^38.0.0-rc.0",
|
|
44
|
+
"@ckeditor/ckeditor5-table": "^38.0.0-rc.0",
|
|
45
|
+
"@ckeditor/ckeditor5-theme-lark": "^38.0.0-rc.0",
|
|
46
|
+
"@ckeditor/ckeditor5-typing": "^38.0.0-rc.0",
|
|
47
|
+
"@ckeditor/ckeditor5-ui": "^38.0.0-rc.0",
|
|
48
|
+
"@ckeditor/ckeditor5-undo": "^38.0.0-rc.0",
|
|
49
|
+
"@ckeditor/ckeditor5-widget": "^38.0.0-rc.0",
|
|
50
50
|
"typescript": "^4.8.4",
|
|
51
51
|
"webpack": "^5.58.1",
|
|
52
52
|
"webpack-cli": "^4.9.0"
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @module engine/controller/editingcontroller
|
|
7
7
|
*/
|
|
8
|
-
import { CKEditorError, ObservableMixin } from '@ckeditor/ckeditor5-utils';
|
|
8
|
+
import { CKEditorError, ObservableMixin, env } from '@ckeditor/ckeditor5-utils';
|
|
9
9
|
import RootEditableElement from '../view/rooteditableelement';
|
|
10
10
|
import View from '../view/view';
|
|
11
11
|
import Mapper from '../conversion/mapper';
|
|
12
12
|
import DowncastDispatcher from '../conversion/downcastdispatcher';
|
|
13
13
|
import { clearAttributes, convertCollapsedSelection, convertRangeSelection, insertAttributesAndChildren, insertText, remove } from '../conversion/downcasthelpers';
|
|
14
14
|
import { convertSelectionChange } from '../conversion/upcasthelpers';
|
|
15
|
+
import { tryFixingRange } from '../model/utils/selection-post-fixer';
|
|
15
16
|
// @if CK_DEBUG_ENGINE // const { dumpTrees, initDocumentDumping } = require( '../dev-utils/utils' );
|
|
16
17
|
/**
|
|
17
18
|
* A controller for the editing pipeline. The editing pipeline controls the {@link ~EditingController#model model} rendering,
|
|
@@ -59,6 +60,8 @@ export default class EditingController extends ObservableMixin() {
|
|
|
59
60
|
}, { priority: 'low' });
|
|
60
61
|
// Convert selection from the view to the model when it changes in the view.
|
|
61
62
|
this.listenTo(this.view.document, 'selectionChange', convertSelectionChange(this.model, this.mapper));
|
|
63
|
+
// Fix `beforeinput` target ranges so that they map to the valid model ranges.
|
|
64
|
+
this.listenTo(this.view.document, 'beforeinput', fixTargetRanges(this.mapper, this.model.schema, this.view), { priority: 'high' });
|
|
62
65
|
// Attach default model converters.
|
|
63
66
|
this.downcastDispatcher.on('insert:$text', insertText(), { priority: 'lowest' });
|
|
64
67
|
this.downcastDispatcher.on('insert', insertAttributesAndChildren(), { priority: 'lowest' });
|
|
@@ -163,3 +166,26 @@ export default class EditingController extends ObservableMixin() {
|
|
|
163
166
|
});
|
|
164
167
|
}
|
|
165
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Checks whether the target ranges provided by the `beforeInput` event can be properly mapped to model ranges and fixes them if needed.
|
|
171
|
+
*
|
|
172
|
+
* This is using the same logic as the selection post-fixer.
|
|
173
|
+
*/
|
|
174
|
+
function fixTargetRanges(mapper, schema, view) {
|
|
175
|
+
return (evt, data) => {
|
|
176
|
+
// The Renderer is disabled while composing on non-android browsers, so we can't be sure that target ranges
|
|
177
|
+
// could be properly mapped to view and model because the DOM and view tree drifted apart.
|
|
178
|
+
if (view.document.isComposing && !env.isAndroid) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (let i = 0; i < data.targetRanges.length; i++) {
|
|
182
|
+
const viewRange = data.targetRanges[i];
|
|
183
|
+
const modelRange = mapper.toModelRange(viewRange);
|
|
184
|
+
const correctedRange = tryFixingRange(modelRange, schema);
|
|
185
|
+
if (!correctedRange || correctedRange.isEqual(modelRange)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
data.targetRanges[i] = mapper.toViewRange(correctedRange);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -72,9 +72,9 @@ export default class Conversion {
|
|
|
72
72
|
addAlias(alias: `${string}Downcast`, dispatcher: DowncastDispatcher): void;
|
|
73
73
|
addAlias(alias: `${string}Upcast`, dispatcher: UpcastDispatcher): void;
|
|
74
74
|
addAlias(alias: string, dispatcher: DowncastDispatcher | UpcastDispatcher): void;
|
|
75
|
-
for(groupName: 'downcast' |
|
|
76
|
-
for(groupName: 'upcast'
|
|
77
|
-
for(groupName:
|
|
75
|
+
for(groupName: 'downcast' | 'dataDowncast' | 'editingDowncast'): DowncastHelpers;
|
|
76
|
+
for(groupName: 'upcast'): UpcastHelpers;
|
|
77
|
+
for<T extends string>(groupName: T): ConversionType<T>;
|
|
78
78
|
/**
|
|
79
79
|
* Sets up converters between the model and the view that convert a model element to a view element (and vice versa).
|
|
80
80
|
* For example, the model `<paragraph>Foo</paragraph>` is turned into `<p>Foo</p>` in the view.
|
|
@@ -474,3 +474,5 @@ export default class Conversion {
|
|
|
474
474
|
*/
|
|
475
475
|
private _createConversionHelpers;
|
|
476
476
|
}
|
|
477
|
+
type ConversionType<T extends string> = T extends `${string}Downcast` ? DowncastHelpers : T extends `${string}Upcast` ? UpcastHelpers : DowncastHelpers | UpcastHelpers;
|
|
478
|
+
export {};
|
|
@@ -1958,7 +1958,7 @@ function createChangeReducerCallback(model) {
|
|
|
1958
1958
|
}
|
|
1959
1959
|
}
|
|
1960
1960
|
else {
|
|
1961
|
-
/* istanbul ignore else: This is always true because otherwise it would not register a reducer callback. */
|
|
1961
|
+
/* istanbul ignore else: This is always true because otherwise it would not register a reducer callback. -- @preserve */
|
|
1962
1962
|
if (model.children) {
|
|
1963
1963
|
return true;
|
|
1964
1964
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { default as AttributeOperation } from './model/operation/attributeoperat
|
|
|
28
28
|
export { default as RenameOperation } from './model/operation/renameoperation';
|
|
29
29
|
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation';
|
|
30
30
|
export { default as RootOperation } from './model/operation/rootoperation';
|
|
31
|
+
export { default as NoOperation } from './model/operation/nooperation';
|
|
31
32
|
export { transformSets } from './model/operation/transform';
|
|
32
33
|
export { default as DocumentSelection, type DocumentSelectionChangeRangeEvent } from './model/documentselection';
|
|
33
34
|
export { default as Range } from './model/range';
|
|
@@ -55,7 +56,7 @@ export type { default as Writer } from './model/writer';
|
|
|
55
56
|
export { findOptimalInsertionRange } from './model/utils/findoptimalinsertionrange';
|
|
56
57
|
export type { DocumentChangeEvent } from './model/document';
|
|
57
58
|
export type { DocumentSelectionChangeEvent } from './model/documentselection';
|
|
58
|
-
export type { ModelApplyOperationEvent, ModelDeleteContentEvent, ModelGetSelectedContentEvent, ModelInsertContentEvent, ModelInsertObjectEvent, ModelModifySelectionEvent } from './model/model';
|
|
59
|
+
export type { ModelApplyOperationEvent, ModelDeleteContentEvent, ModelGetSelectedContentEvent, ModelInsertContentEvent, ModelInsertObjectEvent, ModelModifySelectionEvent, ModelCanEditAtEvent } from './model/model';
|
|
59
60
|
export type { SelectionChangeRangeEvent } from './model/selection';
|
|
60
61
|
export { default as DataTransfer } from './view/datatransfer';
|
|
61
62
|
export { default as DomConverter } from './view/domconverter';
|
|
@@ -66,6 +67,7 @@ export { default as ViewText } from './view/text';
|
|
|
66
67
|
export { default as ViewElement, type ElementAttributes as ViewElementAttributes } from './view/element';
|
|
67
68
|
export { default as ViewContainerElement } from './view/containerelement';
|
|
68
69
|
export { default as ViewEditableElement } from './view/editableelement';
|
|
70
|
+
export { default as ViewRootEditableElement } from './view/rooteditableelement';
|
|
69
71
|
export { default as ViewAttributeElement } from './view/attributeelement';
|
|
70
72
|
export { default as ViewEmptyElement } from './view/emptyelement';
|
|
71
73
|
export { default as ViewRawElement } from './view/rawelement';
|
package/src/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export { default as AttributeOperation } from './model/operation/attributeoperat
|
|
|
22
22
|
export { default as RenameOperation } from './model/operation/renameoperation';
|
|
23
23
|
export { default as RootAttributeOperation } from './model/operation/rootattributeoperation';
|
|
24
24
|
export { default as RootOperation } from './model/operation/rootoperation';
|
|
25
|
+
export { default as NoOperation } from './model/operation/nooperation';
|
|
25
26
|
export { transformSets } from './model/operation/transform';
|
|
26
27
|
// Model.
|
|
27
28
|
export { default as DocumentSelection } from './model/documentselection';
|
|
@@ -47,6 +48,7 @@ export { default as ViewText } from './view/text';
|
|
|
47
48
|
export { default as ViewElement } from './view/element';
|
|
48
49
|
export { default as ViewContainerElement } from './view/containerelement';
|
|
49
50
|
export { default as ViewEditableElement } from './view/editableelement';
|
|
51
|
+
export { default as ViewRootEditableElement } from './view/rooteditableelement';
|
|
50
52
|
export { default as ViewAttributeElement } from './view/attributeelement';
|
|
51
53
|
export { default as ViewEmptyElement } from './view/emptyelement';
|
|
52
54
|
export { default as ViewRawElement } from './view/rawelement';
|
package/src/model/document.d.ts
CHANGED
|
@@ -56,6 +56,14 @@ export default class Document extends Document_base {
|
|
|
56
56
|
* The model differ object. Its role is to buffer changes done on the model document and then calculate a diff of those changes.
|
|
57
57
|
*/
|
|
58
58
|
readonly differ: Differ;
|
|
59
|
+
/**
|
|
60
|
+
* Defines whether the document is in a read-only mode.
|
|
61
|
+
*
|
|
62
|
+
* The user should not be able to change the data of a document that is read-only.
|
|
63
|
+
*
|
|
64
|
+
* @readonly
|
|
65
|
+
*/
|
|
66
|
+
isReadOnly: boolean;
|
|
59
67
|
/**
|
|
60
68
|
* Post-fixer callbacks registered to the model document.
|
|
61
69
|
*/
|
package/src/model/document.js
CHANGED
|
@@ -41,6 +41,7 @@ export default class Document extends EmitterMixin() {
|
|
|
41
41
|
this.selection = new DocumentSelection(this);
|
|
42
42
|
this.roots = new Collection({ idProperty: 'rootName' });
|
|
43
43
|
this.differ = new Differ(model.markers);
|
|
44
|
+
this.isReadOnly = false;
|
|
44
45
|
this._postFixers = new Set();
|
|
45
46
|
this._hasSelectionChangedFromTheLastChangeBlock = false;
|
|
46
47
|
// Graveyard tree root. Document always have a graveyard root, which stores removed nodes.
|
|
@@ -137,9 +137,9 @@ export interface MarkerData {
|
|
|
137
137
|
}
|
|
138
138
|
declare const Marker_base: import("@ckeditor/ckeditor5-utils").Mixed<typeof TypeCheckable, import("@ckeditor/ckeditor5-utils").Emitter>;
|
|
139
139
|
/**
|
|
140
|
-
* `Marker` is a continuous
|
|
141
|
-
* part of model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
|
|
142
|
-
* model document tree, markers are not stored directly in document tree but in
|
|
140
|
+
* `Marker` is a continuous part of the model (like a range), is named and represents some kind of information about the
|
|
141
|
+
* marked part of the model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
|
|
142
|
+
* the model document tree, markers are not stored directly in the document tree but in the
|
|
143
143
|
* {@link module:engine/model/model~Model#markers model markers' collection}. Still, they are document data, by giving
|
|
144
144
|
* additional meaning to the part of a model document between marker start and marker end.
|
|
145
145
|
*
|
|
@@ -214,9 +214,9 @@ export default class MarkerCollection extends EmitterMixin() {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
/**
|
|
217
|
-
* `Marker` is a continuous
|
|
218
|
-
* part of model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
|
|
219
|
-
* model document tree, markers are not stored directly in document tree but in
|
|
217
|
+
* `Marker` is a continuous part of the model (like a range), is named and represents some kind of information about the
|
|
218
|
+
* marked part of the model document. In contrary to {@link module:engine/model/node~Node nodes}, which are building blocks of
|
|
219
|
+
* the model document tree, markers are not stored directly in the document tree but in the
|
|
220
220
|
* {@link module:engine/model/model~Model#markers model markers' collection}. Still, they are document data, by giving
|
|
221
221
|
* additional meaning to the part of a model document between marker start and marker end.
|
|
222
222
|
*
|
package/src/model/model.d.ts
CHANGED
|
@@ -553,6 +553,20 @@ export default class Model extends Model_base {
|
|
|
553
553
|
ignoreWhitespaces?: boolean;
|
|
554
554
|
ignoreMarkers?: boolean;
|
|
555
555
|
}): boolean;
|
|
556
|
+
/**
|
|
557
|
+
* Check whether given selectable is at a place in the model where it can be edited (returns `true`) or not (returns `false`).
|
|
558
|
+
*
|
|
559
|
+
* Should be used instead of {@link module:core/editor/editor~Editor#isReadOnly} to check whether a user action can happen at
|
|
560
|
+
* given selectable. It may be decorated and used differently in different environment (e.g. multi-root editor can disable
|
|
561
|
+
* a particular root).
|
|
562
|
+
*
|
|
563
|
+
* This method is decorated. Although this method accepts any parameter of `Selectable` type, the
|
|
564
|
+
* {@link ~Model#event:canEditAt `canEditAt` event} is fired with `selectable` normalized to an instance of
|
|
565
|
+
* {@link module:engine/model/selection~Selection} or {@link module:engine/model/documentselection~DocumentSelection}
|
|
566
|
+
*
|
|
567
|
+
* @fires canEditAt
|
|
568
|
+
*/
|
|
569
|
+
canEditAt(selectable: Selectable): boolean;
|
|
556
570
|
/**
|
|
557
571
|
* Creates a position from the given root and path in that root.
|
|
558
572
|
*
|
|
@@ -856,7 +870,7 @@ export type ModelInsertObjectEvent = {
|
|
|
856
870
|
* Event fired when {@link ~Model#deleteContent} method is called.
|
|
857
871
|
*
|
|
858
872
|
* The {@link ~Model#deleteContent default action of that method} is implemented as a
|
|
859
|
-
* listener to this event so it can be fully customized by the features.
|
|
873
|
+
* listener to this event, so it can be fully customized by the features.
|
|
860
874
|
*
|
|
861
875
|
* @eventName ~Model#deleteContent
|
|
862
876
|
* @param args The arguments passed to the original method.
|
|
@@ -866,7 +880,7 @@ export type ModelDeleteContentEvent = DecoratedMethodEvent<Model, 'deleteContent
|
|
|
866
880
|
* Event fired when {@link ~Model#modifySelection} method is called.
|
|
867
881
|
*
|
|
868
882
|
* The {@link ~Model#modifySelection default action of that method} is implemented as a
|
|
869
|
-
* listener to this event so it can be fully customized by the features.
|
|
883
|
+
* listener to this event, so it can be fully customized by the features.
|
|
870
884
|
*
|
|
871
885
|
* @eventName ~Model#modifySelection
|
|
872
886
|
* @param args The arguments passed to the original method.
|
|
@@ -876,10 +890,31 @@ export type ModelModifySelectionEvent = DecoratedMethodEvent<Model, 'modifySelec
|
|
|
876
890
|
* Event fired when {@link ~Model#getSelectedContent} method is called.
|
|
877
891
|
*
|
|
878
892
|
* The {@link ~Model#getSelectedContent default action of that method} is implemented as a
|
|
879
|
-
* listener to this event so it can be fully customized by the features.
|
|
893
|
+
* listener to this event, so it can be fully customized by the features.
|
|
880
894
|
*
|
|
881
895
|
* @eventName ~Model#getSelectedContent
|
|
882
896
|
* @param args The arguments passed to the original method.
|
|
883
897
|
*/
|
|
884
898
|
export type ModelGetSelectedContentEvent = DecoratedMethodEvent<Model, 'getSelectedContent'>;
|
|
899
|
+
/**
|
|
900
|
+
* Event fired when {@link ~Model#canEditAt} method is called.
|
|
901
|
+
*
|
|
902
|
+
* The {@link ~Model#canEditAt default action of that method} is implemented as a
|
|
903
|
+
* listener to this event, so it can be fully customized by the features.
|
|
904
|
+
*
|
|
905
|
+
* Although the original method accepts any parameter of `Selectable` type, this event is fired with `selectable` normalized
|
|
906
|
+
* to an instance of {@link module:engine/model/selection~Selection} or {@link module:engine/model/documentselection~DocumentSelection}.
|
|
907
|
+
*
|
|
908
|
+
* @eventName ~Model#canEditAt
|
|
909
|
+
* @param args The arguments passed to the original method.
|
|
910
|
+
*/
|
|
911
|
+
export type ModelCanEditAtEvent = {
|
|
912
|
+
name: 'canEditAt';
|
|
913
|
+
args: [
|
|
914
|
+
[
|
|
915
|
+
selectable?: ModelSelection | DocumentSelection
|
|
916
|
+
]
|
|
917
|
+
];
|
|
918
|
+
return: boolean;
|
|
919
|
+
};
|
|
885
920
|
export {};
|
package/src/model/model.js
CHANGED
|
@@ -106,6 +106,15 @@ export default class Model extends ObservableMixin() {
|
|
|
106
106
|
this.on('insertObject', (evt, [element, selection, options]) => {
|
|
107
107
|
evt.return = insertObject(this, element, selection, options);
|
|
108
108
|
});
|
|
109
|
+
// The base implementation for "decorated" method with remapped arguments.
|
|
110
|
+
this.on('canEditAt', evt => {
|
|
111
|
+
const canEditAt = !this.document.isReadOnly;
|
|
112
|
+
evt.return = canEditAt;
|
|
113
|
+
if (!canEditAt) {
|
|
114
|
+
// Prevent further processing if the selection is at non-editable place.
|
|
115
|
+
evt.stop();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
109
118
|
// @if CK_DEBUG_ENGINE // initDocumentDumping( this.document );
|
|
110
119
|
// @if CK_DEBUG_ENGINE // this.on( 'applyOperation', () => {
|
|
111
120
|
// @if CK_DEBUG_ENGINE // dumpTrees( this.document, this.document.version );
|
|
@@ -168,7 +177,7 @@ export default class Model extends ObservableMixin() {
|
|
|
168
177
|
}
|
|
169
178
|
catch (err) {
|
|
170
179
|
// @if CK_DEBUG // throw err;
|
|
171
|
-
/* istanbul ignore next */
|
|
180
|
+
/* istanbul ignore next -- @preserve */
|
|
172
181
|
CKEditorError.rethrowUnexpectedError(err, this);
|
|
173
182
|
}
|
|
174
183
|
}
|
|
@@ -191,7 +200,7 @@ export default class Model extends ObservableMixin() {
|
|
|
191
200
|
}
|
|
192
201
|
catch (err) {
|
|
193
202
|
// @if CK_DEBUG // throw err;
|
|
194
|
-
/* istanbul ignore next */
|
|
203
|
+
/* istanbul ignore next -- @preserve */
|
|
195
204
|
CKEditorError.rethrowUnexpectedError(err, this);
|
|
196
205
|
}
|
|
197
206
|
}
|
|
@@ -619,6 +628,23 @@ export default class Model extends ObservableMixin() {
|
|
|
619
628
|
}
|
|
620
629
|
return false;
|
|
621
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Check whether given selectable is at a place in the model where it can be edited (returns `true`) or not (returns `false`).
|
|
633
|
+
*
|
|
634
|
+
* Should be used instead of {@link module:core/editor/editor~Editor#isReadOnly} to check whether a user action can happen at
|
|
635
|
+
* given selectable. It may be decorated and used differently in different environment (e.g. multi-root editor can disable
|
|
636
|
+
* a particular root).
|
|
637
|
+
*
|
|
638
|
+
* This method is decorated. Although this method accepts any parameter of `Selectable` type, the
|
|
639
|
+
* {@link ~Model#event:canEditAt `canEditAt` event} is fired with `selectable` normalized to an instance of
|
|
640
|
+
* {@link module:engine/model/selection~Selection} or {@link module:engine/model/documentselection~DocumentSelection}
|
|
641
|
+
*
|
|
642
|
+
* @fires canEditAt
|
|
643
|
+
*/
|
|
644
|
+
canEditAt(selectable) {
|
|
645
|
+
const selection = normalizeSelectable(selectable);
|
|
646
|
+
return this.fire('canEditAt', [selection]);
|
|
647
|
+
}
|
|
622
648
|
/**
|
|
623
649
|
* Creates a position from the given root and path in that root.
|
|
624
650
|
*
|
|
@@ -803,7 +829,15 @@ function normalizeSelectable(selectable, placeOrOffset) {
|
|
|
803
829
|
return selectable;
|
|
804
830
|
}
|
|
805
831
|
if (selectable instanceof Node) {
|
|
806
|
-
|
|
832
|
+
if (placeOrOffset || placeOrOffset === 0) {
|
|
833
|
+
return new ModelSelection(selectable, placeOrOffset);
|
|
834
|
+
}
|
|
835
|
+
else if (selectable.is('rootElement')) {
|
|
836
|
+
return new ModelSelection(selectable, 'in');
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
return new ModelSelection(selectable, 'on');
|
|
840
|
+
}
|
|
807
841
|
}
|
|
808
842
|
return new ModelSelection(selectable);
|
|
809
843
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import Range from '../range';
|
|
10
10
|
import type Document from '../document';
|
|
11
|
+
import type { Selectable } from '../selection';
|
|
11
12
|
/**
|
|
12
13
|
* Operation to change nodes' attribute.
|
|
13
14
|
*
|
|
@@ -62,6 +63,10 @@ export default class AttributeOperation extends Operation {
|
|
|
62
63
|
* @inheritDoc
|
|
63
64
|
*/
|
|
64
65
|
get type(): 'addAttribute' | 'removeAttribute' | 'changeAttribute';
|
|
66
|
+
/**
|
|
67
|
+
* @inheritDoc
|
|
68
|
+
*/
|
|
69
|
+
get affectedSelectable(): Selectable;
|
|
65
70
|
/**
|
|
66
71
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
67
72
|
*/
|
|
@@ -56,6 +56,12 @@ export default class AttributeOperation extends Operation {
|
|
|
56
56
|
return 'changeAttribute';
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* @inheritDoc
|
|
61
|
+
*/
|
|
62
|
+
get affectedSelectable() {
|
|
63
|
+
return this.range.clone();
|
|
64
|
+
}
|
|
59
65
|
/**
|
|
60
66
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
61
67
|
*/
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import type Position from '../position';
|
|
10
|
+
import type { Selectable } from '../selection';
|
|
10
11
|
/**
|
|
11
12
|
* Operation to permanently remove node from detached root.
|
|
12
13
|
* Note this operation is only a local operation and won't be send to the other clients.
|
|
@@ -34,6 +35,10 @@ export default class DetachOperation extends Operation {
|
|
|
34
35
|
* @inheritDoc
|
|
35
36
|
*/
|
|
36
37
|
get type(): 'detach';
|
|
38
|
+
/**
|
|
39
|
+
* @inheritDoc
|
|
40
|
+
*/
|
|
41
|
+
get affectedSelectable(): Selectable;
|
|
37
42
|
/**
|
|
38
43
|
* @inheritDoc
|
|
39
44
|
*/
|
|
@@ -9,6 +9,7 @@ import Operation from './operation';
|
|
|
9
9
|
import Position from '../position';
|
|
10
10
|
import NodeList from '../nodelist';
|
|
11
11
|
import { type NodeSet } from './utils';
|
|
12
|
+
import type { Selectable } from '../selection';
|
|
12
13
|
import type Document from '../document';
|
|
13
14
|
/**
|
|
14
15
|
* Operation to insert one or more nodes at given position in the model.
|
|
@@ -49,6 +50,10 @@ export default class InsertOperation extends Operation {
|
|
|
49
50
|
* Total offset size of inserted nodes.
|
|
50
51
|
*/
|
|
51
52
|
get howMany(): number;
|
|
53
|
+
/**
|
|
54
|
+
* @inheritDoc
|
|
55
|
+
*/
|
|
56
|
+
get affectedSelectable(): Selectable;
|
|
52
57
|
/**
|
|
53
58
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
54
59
|
*/
|
|
@@ -44,6 +44,12 @@ export default class InsertOperation extends Operation {
|
|
|
44
44
|
get howMany() {
|
|
45
45
|
return this.nodes.maxOffset;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* @inheritDoc
|
|
49
|
+
*/
|
|
50
|
+
get affectedSelectable() {
|
|
51
|
+
return this.position.clone();
|
|
52
|
+
}
|
|
47
53
|
/**
|
|
48
54
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
49
55
|
*/
|
|
@@ -9,6 +9,7 @@ import Operation from './operation';
|
|
|
9
9
|
import Range from '../range';
|
|
10
10
|
import type Document from '../document';
|
|
11
11
|
import type MarkerCollection from '../markercollection';
|
|
12
|
+
import type { Selectable } from '../selection';
|
|
12
13
|
export default class MarkerOperation extends Operation {
|
|
13
14
|
/**
|
|
14
15
|
* Marker name.
|
|
@@ -54,6 +55,10 @@ export default class MarkerOperation extends Operation {
|
|
|
54
55
|
* @inheritDoc
|
|
55
56
|
*/
|
|
56
57
|
get type(): 'marker';
|
|
58
|
+
/**
|
|
59
|
+
* @inheritDoc
|
|
60
|
+
*/
|
|
61
|
+
get affectedSelectable(): Selectable;
|
|
57
62
|
/**
|
|
58
63
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
59
64
|
*/
|
|
@@ -32,6 +32,24 @@ export default class MarkerOperation extends Operation {
|
|
|
32
32
|
get type() {
|
|
33
33
|
return 'marker';
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* @inheritDoc
|
|
37
|
+
*/
|
|
38
|
+
get affectedSelectable() {
|
|
39
|
+
const ranges = [];
|
|
40
|
+
if (this.oldRange) {
|
|
41
|
+
ranges.push(this.oldRange.clone());
|
|
42
|
+
}
|
|
43
|
+
if (this.newRange) {
|
|
44
|
+
if (this.oldRange) {
|
|
45
|
+
ranges.push(...this.newRange.getDifference(this.oldRange));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
ranges.push(this.newRange.clone());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ranges;
|
|
52
|
+
}
|
|
35
53
|
/**
|
|
36
54
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
37
55
|
*/
|
|
@@ -9,6 +9,7 @@ import Operation from './operation';
|
|
|
9
9
|
import Position from '../position';
|
|
10
10
|
import Range from '../range';
|
|
11
11
|
import type Document from '../document';
|
|
12
|
+
import type { Selectable } from '../selection';
|
|
12
13
|
/**
|
|
13
14
|
* Operation to merge two {@link module:engine/model/element~Element elements}.
|
|
14
15
|
*
|
|
@@ -59,6 +60,10 @@ export default class MergeOperation extends Operation {
|
|
|
59
60
|
* The range starts at {@link ~MergeOperation#sourcePosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.
|
|
60
61
|
*/
|
|
61
62
|
get movedRange(): Range;
|
|
63
|
+
/**
|
|
64
|
+
* @inheritDoc
|
|
65
|
+
*/
|
|
66
|
+
get affectedSelectable(): Selectable;
|
|
62
67
|
/**
|
|
63
68
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
64
69
|
*/
|
|
@@ -63,6 +63,18 @@ export default class MergeOperation extends Operation {
|
|
|
63
63
|
const end = this.sourcePosition.getShiftedBy(Number.POSITIVE_INFINITY);
|
|
64
64
|
return new Range(this.sourcePosition, end);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* @inheritDoc
|
|
68
|
+
*/
|
|
69
|
+
get affectedSelectable() {
|
|
70
|
+
const mergedElement = this.sourcePosition.parent;
|
|
71
|
+
return [
|
|
72
|
+
Range._createOn(mergedElement),
|
|
73
|
+
// These could be positions but `Selectable` type only supports `Iterable<Range>`.
|
|
74
|
+
Range._createFromPositionAndShift(this.targetPosition, 0),
|
|
75
|
+
Range._createFromPositionAndShift(this.graveyardPosition, 0)
|
|
76
|
+
];
|
|
77
|
+
}
|
|
66
78
|
/**
|
|
67
79
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
68
80
|
*/
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import Position from '../position';
|
|
10
|
+
import type { Selectable } from '../selection';
|
|
10
11
|
import type Document from '../document';
|
|
11
12
|
/**
|
|
12
13
|
* Operation to move a range of {@link module:engine/model/item~Item model items}
|
|
@@ -40,6 +41,10 @@ export default class MoveOperation extends Operation {
|
|
|
40
41
|
* @inheritDoc
|
|
41
42
|
*/
|
|
42
43
|
get type(): 'move' | 'remove' | 'reinsert';
|
|
44
|
+
/**
|
|
45
|
+
* @inheritDoc
|
|
46
|
+
*/
|
|
47
|
+
get affectedSelectable(): Selectable;
|
|
43
48
|
/**
|
|
44
49
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
45
50
|
*/
|
|
@@ -47,6 +47,15 @@ export default class MoveOperation extends Operation {
|
|
|
47
47
|
}
|
|
48
48
|
return 'move';
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* @inheritDoc
|
|
52
|
+
*/
|
|
53
|
+
get affectedSelectable() {
|
|
54
|
+
return [
|
|
55
|
+
Range._createFromPositionAndShift(this.sourcePosition, this.howMany),
|
|
56
|
+
Range._createFromPositionAndShift(this.targetPosition, 0)
|
|
57
|
+
];
|
|
58
|
+
}
|
|
50
59
|
/**
|
|
51
60
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
52
61
|
*/
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module engine/model/operation/nooperation
|
|
7
7
|
*/
|
|
8
8
|
import Operation from './operation';
|
|
9
|
+
import type { Selectable } from '../selection';
|
|
9
10
|
/**
|
|
10
11
|
* Operation which is doing nothing ("empty operation", "do-nothing operation", "noop"). This is an operation,
|
|
11
12
|
* which when executed does not change the tree model. It still has some parameters defined for transformation purposes.
|
|
@@ -16,6 +17,10 @@ import Operation from './operation';
|
|
|
16
17
|
*/
|
|
17
18
|
export default class NoOperation extends Operation {
|
|
18
19
|
get type(): 'noop';
|
|
20
|
+
/**
|
|
21
|
+
* @inheritDoc
|
|
22
|
+
*/
|
|
23
|
+
get affectedSelectable(): Selectable;
|
|
19
24
|
/**
|
|
20
25
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
21
26
|
*/
|
|
@@ -18,6 +18,12 @@ export default class NoOperation extends Operation {
|
|
|
18
18
|
get type() {
|
|
19
19
|
return 'noop';
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* @inheritDoc
|
|
23
|
+
*/
|
|
24
|
+
get affectedSelectable() {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
21
27
|
/**
|
|
22
28
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
23
29
|
*/
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type Batch from '../batch';
|
|
6
6
|
import type Document from '../document';
|
|
7
|
+
import type { Selectable } from '../selection';
|
|
7
8
|
/**
|
|
8
9
|
* @module engine/model/operation/operation
|
|
9
10
|
*/
|
|
@@ -38,6 +39,12 @@ export default abstract class Operation {
|
|
|
38
39
|
* can be applied or `null` if the operation operates on detached (non-document) tree.
|
|
39
40
|
*/
|
|
40
41
|
constructor(baseVersion: number | null);
|
|
42
|
+
/**
|
|
43
|
+
* A selectable that will be affected by the operation after it is executed.
|
|
44
|
+
*
|
|
45
|
+
* The exact returned parameter differs between operation types.
|
|
46
|
+
*/
|
|
47
|
+
abstract get affectedSelectable(): Selectable;
|
|
41
48
|
/**
|
|
42
49
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
43
50
|
*
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import Position from '../position';
|
|
10
10
|
import type Document from '../document';
|
|
11
|
+
import type { Selectable } from '../selection';
|
|
11
12
|
/**
|
|
12
13
|
* Operation to change element's name.
|
|
13
14
|
*
|
|
@@ -40,6 +41,10 @@ export default class RenameOperation extends Operation {
|
|
|
40
41
|
* @inheritDoc
|
|
41
42
|
*/
|
|
42
43
|
get type(): 'rename';
|
|
44
|
+
/**
|
|
45
|
+
* @inheritDoc
|
|
46
|
+
*/
|
|
47
|
+
get affectedSelectable(): Selectable;
|
|
43
48
|
/**
|
|
44
49
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
45
50
|
*
|
|
@@ -38,6 +38,12 @@ export default class RenameOperation extends Operation {
|
|
|
38
38
|
get type() {
|
|
39
39
|
return 'rename';
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* @inheritDoc
|
|
43
|
+
*/
|
|
44
|
+
get affectedSelectable() {
|
|
45
|
+
return this.position.nodeAfter;
|
|
46
|
+
}
|
|
41
47
|
/**
|
|
42
48
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
43
49
|
*
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import type Document from '../document';
|
|
10
10
|
import type RootElement from '../rootelement';
|
|
11
|
+
import type { Selectable } from '../selection';
|
|
11
12
|
/**
|
|
12
13
|
* Operation to change root element's attribute. Using this class you can add, remove or change value of the attribute.
|
|
13
14
|
*
|
|
@@ -55,6 +56,10 @@ export default class RootAttributeOperation extends Operation {
|
|
|
55
56
|
* @inheritDoc
|
|
56
57
|
*/
|
|
57
58
|
get type(): 'addRootAttribute' | 'removeRootAttribute' | 'changeRootAttribute';
|
|
59
|
+
/**
|
|
60
|
+
* @inheritDoc
|
|
61
|
+
*/
|
|
62
|
+
get affectedSelectable(): Selectable;
|
|
58
63
|
/**
|
|
59
64
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
60
65
|
*
|
|
@@ -50,6 +50,12 @@ export default class RootAttributeOperation extends Operation {
|
|
|
50
50
|
return 'changeRootAttribute';
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* @inheritDoc
|
|
55
|
+
*/
|
|
56
|
+
get affectedSelectable() {
|
|
57
|
+
return this.root;
|
|
58
|
+
}
|
|
53
59
|
/**
|
|
54
60
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
55
61
|
*
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import Operation from './operation';
|
|
9
9
|
import type Document from '../document';
|
|
10
|
+
import type { Selectable } from '../selection';
|
|
10
11
|
/**
|
|
11
12
|
* Operation that creates (or attaches) or detaches a root element.
|
|
12
13
|
*/
|
|
@@ -41,6 +42,10 @@ export default class RootOperation extends Operation {
|
|
|
41
42
|
* @inheritDoc
|
|
42
43
|
*/
|
|
43
44
|
get type(): 'addRoot' | 'detachRoot';
|
|
45
|
+
/**
|
|
46
|
+
* @inheritDoc
|
|
47
|
+
*/
|
|
48
|
+
get affectedSelectable(): Selectable;
|
|
44
49
|
/**
|
|
45
50
|
* @inheritDoc
|
|
46
51
|
*/
|
|
@@ -41,6 +41,12 @@ export default class RootOperation extends Operation {
|
|
|
41
41
|
get type() {
|
|
42
42
|
return this.isAdd ? 'addRoot' : 'detachRoot';
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* @inheritDoc
|
|
46
|
+
*/
|
|
47
|
+
get affectedSelectable() {
|
|
48
|
+
return this._document.getRoot(this.rootName);
|
|
49
|
+
}
|
|
44
50
|
/**
|
|
45
51
|
* @inheritDoc
|
|
46
52
|
*/
|
|
@@ -9,6 +9,7 @@ import Operation from './operation';
|
|
|
9
9
|
import Position from '../position';
|
|
10
10
|
import Range from '../range';
|
|
11
11
|
import type Document from '../document';
|
|
12
|
+
import type { Selectable } from '../selection';
|
|
12
13
|
/**
|
|
13
14
|
* Operation to split {@link module:engine/model/element~Element an element} at given
|
|
14
15
|
* {@link module:engine/model/operation/splitoperation~SplitOperation#splitPosition split position} into two elements,
|
|
@@ -61,6 +62,10 @@ export default class SplitOperation extends Operation {
|
|
|
61
62
|
* The range starts at {@link #splitPosition} and ends in the same parent, at `POSITIVE_INFINITY` offset.
|
|
62
63
|
*/
|
|
63
64
|
get movedRange(): Range;
|
|
65
|
+
/**
|
|
66
|
+
* @inheritDoc
|
|
67
|
+
*/
|
|
68
|
+
get affectedSelectable(): Selectable;
|
|
64
69
|
/**
|
|
65
70
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
66
71
|
*
|
|
@@ -65,6 +65,20 @@ export default class SplitOperation extends Operation {
|
|
|
65
65
|
const end = this.splitPosition.getShiftedBy(Number.POSITIVE_INFINITY);
|
|
66
66
|
return new Range(this.splitPosition, end);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* @inheritDoc
|
|
70
|
+
*/
|
|
71
|
+
get affectedSelectable() {
|
|
72
|
+
// These could be positions but `Selectable` type only supports `Iterable<Range>`.
|
|
73
|
+
const ranges = [
|
|
74
|
+
Range._createFromPositionAndShift(this.splitPosition, 0),
|
|
75
|
+
Range._createFromPositionAndShift(this.insertionPosition, 0)
|
|
76
|
+
];
|
|
77
|
+
if (this.graveyardPosition) {
|
|
78
|
+
ranges.push(Range._createFromPositionAndShift(this.graveyardPosition, 0));
|
|
79
|
+
}
|
|
80
|
+
return ranges;
|
|
81
|
+
}
|
|
68
82
|
/**
|
|
69
83
|
* Creates and returns an operation that has the same parameters as this operation.
|
|
70
84
|
*
|
package/src/model/selection.d.ts
CHANGED
|
@@ -362,13 +362,23 @@ export default class Selection extends Selection_base {
|
|
|
362
362
|
* </block>
|
|
363
363
|
* ```
|
|
364
364
|
*
|
|
365
|
-
* **Special case**:
|
|
366
|
-
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
|
|
365
|
+
* **Special case**: Selection ignores first and/or last blocks if nothing (from user perspective) is selected in them.
|
|
367
366
|
*
|
|
368
367
|
* ```xml
|
|
368
|
+
* // Selection ends and the beginning of the last block.
|
|
369
369
|
* <paragraph>[a</paragraph>
|
|
370
370
|
* <paragraph>b</paragraph>
|
|
371
|
-
* <paragraph>]c</paragraph> //
|
|
371
|
+
* <paragraph>]c</paragraph> // This block will not be returned
|
|
372
|
+
*
|
|
373
|
+
* // Selection begins at the end of the first block.
|
|
374
|
+
* <paragraph>a[</paragraph> // This block will not be returned
|
|
375
|
+
* <paragraph>b</paragraph>
|
|
376
|
+
* <paragraph>c]</paragraph>
|
|
377
|
+
*
|
|
378
|
+
* // Selection begings at the end of the first block and ends at the beginning of the last block.
|
|
379
|
+
* <paragraph>a[</paragraph> // This block will not be returned
|
|
380
|
+
* <paragraph>b</paragraph>
|
|
381
|
+
* <paragraph>]c</paragraph> // This block will not be returned
|
|
372
382
|
* ```
|
|
373
383
|
*/
|
|
374
384
|
getSelectedBlocks(): IterableIterator<Element>;
|
package/src/model/selection.js
CHANGED
|
@@ -557,13 +557,23 @@ export default class Selection extends EmitterMixin(TypeCheckable) {
|
|
|
557
557
|
* </block>
|
|
558
558
|
* ```
|
|
559
559
|
*
|
|
560
|
-
* **Special case**:
|
|
561
|
-
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
|
|
560
|
+
* **Special case**: Selection ignores first and/or last blocks if nothing (from user perspective) is selected in them.
|
|
562
561
|
*
|
|
563
562
|
* ```xml
|
|
563
|
+
* // Selection ends and the beginning of the last block.
|
|
564
564
|
* <paragraph>[a</paragraph>
|
|
565
565
|
* <paragraph>b</paragraph>
|
|
566
|
-
* <paragraph>]c</paragraph> //
|
|
566
|
+
* <paragraph>]c</paragraph> // This block will not be returned
|
|
567
|
+
*
|
|
568
|
+
* // Selection begins at the end of the first block.
|
|
569
|
+
* <paragraph>a[</paragraph> // This block will not be returned
|
|
570
|
+
* <paragraph>b</paragraph>
|
|
571
|
+
* <paragraph>c]</paragraph>
|
|
572
|
+
*
|
|
573
|
+
* // Selection begings at the end of the first block and ends at the beginning of the last block.
|
|
574
|
+
* <paragraph>a[</paragraph> // This block will not be returned
|
|
575
|
+
* <paragraph>b</paragraph>
|
|
576
|
+
* <paragraph>]c</paragraph> // This block will not be returned
|
|
567
577
|
* ```
|
|
568
578
|
*/
|
|
569
579
|
*getSelectedBlocks() {
|
|
@@ -571,7 +581,7 @@ export default class Selection extends EmitterMixin(TypeCheckable) {
|
|
|
571
581
|
for (const range of this.getRanges()) {
|
|
572
582
|
// Get start block of range in case of a collapsed range.
|
|
573
583
|
const startBlock = getParentBlock(range.start, visited);
|
|
574
|
-
if (
|
|
584
|
+
if (isStartBlockSelected(startBlock, range)) {
|
|
575
585
|
yield startBlock;
|
|
576
586
|
}
|
|
577
587
|
for (const value of range.getWalker()) {
|
|
@@ -581,8 +591,7 @@ export default class Selection extends EmitterMixin(TypeCheckable) {
|
|
|
581
591
|
}
|
|
582
592
|
}
|
|
583
593
|
const endBlock = getParentBlock(range.end, visited);
|
|
584
|
-
|
|
585
|
-
if (endBlock && !range.end.isTouching(Position._createAt(endBlock, 0)) && isTopBlockInRange(endBlock, range)) {
|
|
594
|
+
if (isEndBlockSelected(endBlock, range)) {
|
|
586
595
|
yield endBlock;
|
|
587
596
|
}
|
|
588
597
|
}
|
|
@@ -709,6 +718,62 @@ function isTopBlockInRange(block, range) {
|
|
|
709
718
|
const isParentInRange = range.containsRange(Range._createOn(parentBlock), true);
|
|
710
719
|
return !isParentInRange;
|
|
711
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* If a selection starts at the end of a block, that block is not returned as from the user's perspective this block wasn't selected.
|
|
723
|
+
* See [#11585](https://github.com/ckeditor/ckeditor5/issues/11585) for more details.
|
|
724
|
+
*
|
|
725
|
+
* ```xml
|
|
726
|
+
* <paragraph>a[</paragraph> // This block will not be returned
|
|
727
|
+
* <paragraph>b</paragraph>
|
|
728
|
+
* <paragraph>c]</paragraph>
|
|
729
|
+
* ```
|
|
730
|
+
*
|
|
731
|
+
* Collapsed selection is not affected by it:
|
|
732
|
+
*
|
|
733
|
+
* ```xml
|
|
734
|
+
* <paragraph>a[]</paragraph> // This block will be returned
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
function isStartBlockSelected(startBlock, range) {
|
|
738
|
+
if (!startBlock) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
if (range.isCollapsed || startBlock.isEmpty) {
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
if (range.start.isTouching(Position._createAt(startBlock, startBlock.maxOffset))) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
return isTopBlockInRange(startBlock, range);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* If a selection ends at the beginning of a block, that block is not returned as from the user's perspective this block wasn't selected.
|
|
751
|
+
* See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
|
|
752
|
+
*
|
|
753
|
+
* ```xml
|
|
754
|
+
* <paragraph>[a</paragraph>
|
|
755
|
+
* <paragraph>b</paragraph>
|
|
756
|
+
* <paragraph>]c</paragraph> // this block will not be returned
|
|
757
|
+
* ```
|
|
758
|
+
*
|
|
759
|
+
* Collapsed selection is not affected by it:
|
|
760
|
+
*
|
|
761
|
+
* ```xml
|
|
762
|
+
* <paragraph>[]a</paragraph> // this block will be returned
|
|
763
|
+
* ```
|
|
764
|
+
*/
|
|
765
|
+
function isEndBlockSelected(endBlock, range) {
|
|
766
|
+
if (!endBlock) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
if (range.isCollapsed || endBlock.isEmpty) {
|
|
770
|
+
return true;
|
|
771
|
+
}
|
|
772
|
+
if (range.end.isTouching(Position._createAt(endBlock, 0))) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
return isTopBlockInRange(endBlock, range);
|
|
776
|
+
}
|
|
712
777
|
/**
|
|
713
778
|
* Returns first ancestor block of a node.
|
|
714
779
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
export default class TypeCheckable {
|
|
6
|
-
/* istanbul ignore next */
|
|
6
|
+
/* istanbul ignore next -- @preserve */
|
|
7
7
|
is() {
|
|
8
8
|
// There are a lot of overloads above.
|
|
9
9
|
// Overriding method in derived classes remove them and only `is( type: string ): boolean` is visible which we don't want.
|
|
@@ -162,7 +162,7 @@ export default function insertContent(model, content, selectable) {
|
|
|
162
162
|
selectionLiveRange.detach();
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
/* istanbul ignore else */
|
|
165
|
+
/* istanbul ignore else -- @preserve */
|
|
166
166
|
if (newRange) {
|
|
167
167
|
if (selection instanceof DocumentSelection) {
|
|
168
168
|
writer.setSelection(newRange);
|
|
@@ -253,7 +253,7 @@ class Insertion {
|
|
|
253
253
|
// If the real end was after the last auto paragraph then update relevant properties.
|
|
254
254
|
if (positionAfterNode.isAfter(positionAfterLastNode)) {
|
|
255
255
|
this._lastNode = node;
|
|
256
|
-
/* istanbul ignore if */
|
|
256
|
+
/* istanbul ignore if -- @preserve */
|
|
257
257
|
if (this.position.parent != node || !this.position.isAtEnd) {
|
|
258
258
|
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
|
|
259
259
|
// At this point the insertion position should be at the end of the last auto paragraph.
|
|
@@ -386,7 +386,7 @@ class Insertion {
|
|
|
386
386
|
* @param node The node to insert.
|
|
387
387
|
*/
|
|
388
388
|
_appendToFragment(node) {
|
|
389
|
-
/* istanbul ignore if */
|
|
389
|
+
/* istanbul ignore if -- @preserve */
|
|
390
390
|
if (!this.schema.checkChild(this.position, node)) {
|
|
391
391
|
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
|
|
392
392
|
// Note that it would often be a silent issue if we insert node in a place where it's not allowed.
|
|
@@ -517,7 +517,7 @@ class Insertion {
|
|
|
517
517
|
}
|
|
518
518
|
const mergePosRight = LivePosition._createAfter(node);
|
|
519
519
|
mergePosRight.stickiness = 'toNext';
|
|
520
|
-
/* istanbul ignore if */
|
|
520
|
+
/* istanbul ignore if -- @preserve */
|
|
521
521
|
if (!this.position.isEqual(mergePosRight)) {
|
|
522
522
|
// Algorithm's correctness check. We should never end up here but it's good to know that we did.
|
|
523
523
|
// At this point the insertion position should be after the node we'll merge. If it isn't,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import Range from '../range';
|
|
6
6
|
import type Model from '../model';
|
|
7
|
+
import type Schema from '../schema';
|
|
7
8
|
/**
|
|
8
9
|
* Injects selection post-fixer to the model.
|
|
9
10
|
*
|
|
@@ -56,6 +57,14 @@ import type Model from '../model';
|
|
|
56
57
|
* them to select `isLimit=true` elements.
|
|
57
58
|
*/
|
|
58
59
|
export declare function injectSelectionPostFixer(model: Model): void;
|
|
60
|
+
/**
|
|
61
|
+
* Tries fixing a range if it's incorrect.
|
|
62
|
+
*
|
|
63
|
+
* **Note:** This helper is used by the selection post-fixer and to fix the `beforeinput` target ranges.
|
|
64
|
+
*
|
|
65
|
+
* @returns Returns fixed range or null if range is valid.
|
|
66
|
+
*/
|
|
67
|
+
export declare function tryFixingRange(range: Range, schema: Schema): Range | null;
|
|
59
68
|
/**
|
|
60
69
|
* Returns a minimal non-intersecting array of ranges without duplicates.
|
|
61
70
|
*
|
|
@@ -97,9 +97,11 @@ function selectionPostFixer(writer, model) {
|
|
|
97
97
|
/**
|
|
98
98
|
* Tries fixing a range if it's incorrect.
|
|
99
99
|
*
|
|
100
|
+
* **Note:** This helper is used by the selection post-fixer and to fix the `beforeinput` target ranges.
|
|
101
|
+
*
|
|
100
102
|
* @returns Returns fixed range or null if range is valid.
|
|
101
103
|
*/
|
|
102
|
-
function tryFixingRange(range, schema) {
|
|
104
|
+
export function tryFixingRange(range, schema) {
|
|
103
105
|
if (range.isCollapsed) {
|
|
104
106
|
return tryFixingCollapsedRange(range, schema);
|
|
105
107
|
}
|
package/src/model/writer.d.ts
CHANGED
|
@@ -472,7 +472,7 @@ export default class Writer {
|
|
|
472
472
|
* @param element The element to rename.
|
|
473
473
|
* @param newName New element name.
|
|
474
474
|
*/
|
|
475
|
-
rename(element: Element, newName: string): void;
|
|
475
|
+
rename(element: Element | DocumentFragment, newName: string): void;
|
|
476
476
|
/**
|
|
477
477
|
* Splits elements starting from the given position and going to the top of the model tree as long as given
|
|
478
478
|
* `limitElement` is reached. When `limitElement` is not defined then only the parent of the given position will be split.
|
|
@@ -59,6 +59,10 @@ export default class DataTransfer {
|
|
|
59
59
|
*/
|
|
60
60
|
set dropEffect(value: DropEffect);
|
|
61
61
|
get dropEffect(): DropEffect;
|
|
62
|
+
/**
|
|
63
|
+
* Set a preview image of the dragged content.
|
|
64
|
+
*/
|
|
65
|
+
setDragImage(image: Element, x: number, y: number): void;
|
|
62
66
|
/**
|
|
63
67
|
* Whether the dragging operation was canceled.
|
|
64
68
|
*/
|
package/src/view/datatransfer.js
CHANGED
|
@@ -71,6 +71,12 @@ export default class DataTransfer {
|
|
|
71
71
|
get dropEffect() {
|
|
72
72
|
return this._native.dropEffect;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Set a preview image of the dragged content.
|
|
76
|
+
*/
|
|
77
|
+
setDragImage(image, x, y) {
|
|
78
|
+
this._native.setDragImage(image, x, y);
|
|
79
|
+
}
|
|
74
80
|
/**
|
|
75
81
|
* Whether the dragging operation was canceled.
|
|
76
82
|
*/
|
package/src/view/domconverter.js
CHANGED
|
@@ -16,7 +16,7 @@ import ViewDocumentFragment from './documentfragment';
|
|
|
16
16
|
import ViewTreeWalker from './treewalker';
|
|
17
17
|
import { default as Matcher } from './matcher';
|
|
18
18
|
import { BR_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER, MARKED_NBSP_FILLER, getDataWithoutFiller, isInlineFiller, startsWithFiller } from './filler';
|
|
19
|
-
import { global, logWarning, indexOf, getAncestors, isText, isComment, first } from '@ckeditor/ckeditor5-utils';
|
|
19
|
+
import { global, logWarning, indexOf, getAncestors, isText, isComment, isValidAttributeName, first } from '@ckeditor/ckeditor5-utils';
|
|
20
20
|
const BR_FILLER_REF = BR_FILLER(global.document); // eslint-disable-line new-cap
|
|
21
21
|
const NBSP_FILLER_REF = NBSP_FILLER(global.document); // eslint-disable-line new-cap
|
|
22
22
|
const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER(global.document); // eslint-disable-line new-cap
|
|
@@ -305,6 +305,15 @@ export default class DomConverter {
|
|
|
305
305
|
if (!shouldRenderAttribute) {
|
|
306
306
|
logWarning('domconverter-unsafe-attribute-detected', { domElement, key, value });
|
|
307
307
|
}
|
|
308
|
+
if (!isValidAttributeName(key)) {
|
|
309
|
+
/**
|
|
310
|
+
* Invalid attribute name was ignored during rendering.
|
|
311
|
+
*
|
|
312
|
+
* @error domconverter-invalid-attribute-detected
|
|
313
|
+
*/
|
|
314
|
+
logWarning('domconverter-invalid-attribute-detected', { domElement, key, value });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
308
317
|
// The old value was safe but the new value is unsafe.
|
|
309
318
|
if (domElement.hasAttribute(key) && !shouldRenderAttribute) {
|
|
310
319
|
domElement.removeAttribute(key);
|
|
@@ -641,7 +650,8 @@ export default class DomConverter {
|
|
|
641
650
|
}
|
|
642
651
|
else {
|
|
643
652
|
const domBefore = domParent.childNodes[domOffset - 1];
|
|
644
|
-
|
|
653
|
+
// Jump over an inline filler (and also on Firefox jump over a block filler while pressing backspace in an empty paragraph).
|
|
654
|
+
if (isText(domBefore) && isInlineFiller(domBefore) || domBefore && this.isBlockFiller(domBefore)) {
|
|
645
655
|
return this.domPositionToView(domBefore.parentNode, indexOf(domBefore));
|
|
646
656
|
}
|
|
647
657
|
const viewBefore = isText(domBefore) ?
|
package/src/view/matcher.js
CHANGED
|
@@ -423,7 +423,7 @@ function matchAttributes(patterns, element) {
|
|
|
423
423
|
*/
|
|
424
424
|
function matchClasses(patterns, element) {
|
|
425
425
|
// We don't need `getter` here because patterns for classes are always normalized to `[ className, true ]`.
|
|
426
|
-
return matchPatterns(patterns, element.getClassNames(), /* istanbul ignore next */ () => { });
|
|
426
|
+
return matchPatterns(patterns, element.getClassNames(), /* istanbul ignore next -- @preserve */ () => { });
|
|
427
427
|
}
|
|
428
428
|
/**
|
|
429
429
|
* Checks if styles of provided element can be matched against provided patterns.
|
|
@@ -116,7 +116,7 @@ export default class SelectionObserver extends Observer {
|
|
|
116
116
|
this._fireSelectionChangeDoneDebounced.cancel();
|
|
117
117
|
this._documentIsSelectingInactivityTimeoutDebounced.cancel();
|
|
118
118
|
}
|
|
119
|
-
/* istanbul ignore next */
|
|
119
|
+
/* istanbul ignore next -- @preserve */
|
|
120
120
|
_reportInfiniteLoop() {
|
|
121
121
|
// @if CK_DEBUG // throw new Error(
|
|
122
122
|
// @if CK_DEBUG // 'Selection change observer detected an infinite rendering loop.\n\n' +
|
package/src/view/position.js
CHANGED
|
@@ -209,11 +209,8 @@ export default class Position extends TypeCheckable {
|
|
|
209
209
|
return 'before';
|
|
210
210
|
case 'extension':
|
|
211
211
|
return 'after';
|
|
212
|
-
/* istanbul ignore next */
|
|
213
|
-
case 'same':
|
|
214
|
-
// Already covered by `this.isEqual` above. Added so TypeScript can infer `result` as number in `default` case.
|
|
215
|
-
return 'same';
|
|
216
212
|
default:
|
|
213
|
+
// Cast to number to avoid having 'same' as a type of `result`.
|
|
217
214
|
return thisPath[result] < otherPath[result] ? 'before' : 'after';
|
|
218
215
|
}
|
|
219
216
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module engine/view/typecheckable
|
|
7
7
|
*/
|
|
8
8
|
export default class TypeCheckable {
|
|
9
|
-
/* istanbul ignore next */
|
|
9
|
+
/* istanbul ignore next -- @preserve */
|
|
10
10
|
is() {
|
|
11
11
|
// There are a lot of overloads above.
|
|
12
12
|
// Overriding method in derived classes remove them and only `is( type: string ): boolean` is visible which we don't want.
|
package/src/view/view.js
CHANGED