@ckeditor/ckeditor5-engine 29.1.0 → 31.1.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 -20
- package/src/controller/datacontroller.js +50 -1
- package/src/dataprocessor/htmldataprocessor.js +10 -13
- package/src/dataprocessor/xmldataprocessor.js +12 -16
- package/src/dev-utils/view.js +21 -3
- package/src/model/markercollection.js +5 -4
- package/src/model/range.js +1 -1
- package/src/model/selection.js +1 -1
- package/src/model/utils/selection-post-fixer.js +43 -28
- package/src/view/document.js +12 -0
- package/src/view/domconverter.js +262 -9
- package/src/view/downcastwriter.js +32 -1
- package/src/view/element.js +28 -0
- package/src/view/matcher.js +21 -16
- package/src/view/observer/selectionobserver.js +48 -1
- package/src/view/rawelement.js +3 -2
- package/src/view/renderer.js +91 -38
- package/src/view/styles/utils.js +5 -0
- package/src/view/uielement.js +5 -2
- package/src/view/view.js +1 -1
- package/theme/renderer.css +9 -0
- package/CHANGELOG.md +0 -823
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "31.1.0",
|
|
4
4
|
"description": "The editing engine of CKEditor 5 – the best browser-based rich text editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wysiwyg",
|
|
@@ -23,28 +23,30 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "src/index.js",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@ckeditor/ckeditor5-utils": "^
|
|
26
|
+
"@ckeditor/ckeditor5-utils": "^31.1.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-
|
|
34
|
-
"@ckeditor/ckeditor5-
|
|
35
|
-
"@ckeditor/ckeditor5-
|
|
36
|
-
"@ckeditor/ckeditor5-
|
|
37
|
-
"@ckeditor/ckeditor5-
|
|
38
|
-
"@ckeditor/ckeditor5-
|
|
39
|
-
"@ckeditor/ckeditor5-
|
|
40
|
-
"@ckeditor/ckeditor5-
|
|
41
|
-
"@ckeditor/ckeditor5-
|
|
42
|
-
"@ckeditor/ckeditor5-
|
|
43
|
-
"@ckeditor/ckeditor5-
|
|
44
|
-
"@ckeditor/ckeditor5-
|
|
45
|
-
"@ckeditor/ckeditor5-
|
|
46
|
-
"@ckeditor/ckeditor5-
|
|
47
|
-
"@ckeditor/ckeditor5-
|
|
30
|
+
"@ckeditor/ckeditor5-basic-styles": "^31.1.0",
|
|
31
|
+
"@ckeditor/ckeditor5-block-quote": "^31.1.0",
|
|
32
|
+
"@ckeditor/ckeditor5-clipboard": "^31.1.0",
|
|
33
|
+
"@ckeditor/ckeditor5-cloud-services": "^31.1.0",
|
|
34
|
+
"@ckeditor/ckeditor5-core": "^31.1.0",
|
|
35
|
+
"@ckeditor/ckeditor5-editor-classic": "^31.1.0",
|
|
36
|
+
"@ckeditor/ckeditor5-enter": "^31.1.0",
|
|
37
|
+
"@ckeditor/ckeditor5-essentials": "^31.1.0",
|
|
38
|
+
"@ckeditor/ckeditor5-heading": "^31.1.0",
|
|
39
|
+
"@ckeditor/ckeditor5-image": "^31.1.0",
|
|
40
|
+
"@ckeditor/ckeditor5-link": "^31.1.0",
|
|
41
|
+
"@ckeditor/ckeditor5-list": "^31.1.0",
|
|
42
|
+
"@ckeditor/ckeditor5-mention": "^31.1.0",
|
|
43
|
+
"@ckeditor/ckeditor5-paragraph": "^31.1.0",
|
|
44
|
+
"@ckeditor/ckeditor5-table": "^31.1.0",
|
|
45
|
+
"@ckeditor/ckeditor5-theme-lark": "^31.1.0",
|
|
46
|
+
"@ckeditor/ckeditor5-typing": "^31.1.0",
|
|
47
|
+
"@ckeditor/ckeditor5-ui": "^31.1.0",
|
|
48
|
+
"@ckeditor/ckeditor5-undo": "^31.1.0",
|
|
49
|
+
"@ckeditor/ckeditor5-widget": "^31.1.0",
|
|
48
50
|
"webpack": "^4.43.0",
|
|
49
51
|
"webpack-cli": "^3.3.11"
|
|
50
52
|
},
|
|
@@ -145,6 +145,7 @@ export default class DataController {
|
|
|
145
145
|
|
|
146
146
|
this.decorate( 'init' );
|
|
147
147
|
this.decorate( 'set' );
|
|
148
|
+
this.decorate( 'get' );
|
|
148
149
|
|
|
149
150
|
// Fire the `ready` event when the initialization has completed. Such low-level listener gives possibility
|
|
150
151
|
// to plug into the initialization pipeline without interrupting the initialization flow.
|
|
@@ -163,6 +164,7 @@ export default class DataController {
|
|
|
163
164
|
* Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
|
|
164
165
|
* formatted by the {@link #processor data processor}.
|
|
165
166
|
*
|
|
167
|
+
* @fires get
|
|
166
168
|
* @param {Object} [options] Additional configuration for the retrieved data. `DataController` provides two optional
|
|
167
169
|
* properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.
|
|
168
170
|
* @param {String} [options.rootName='main'] Root name.
|
|
@@ -523,6 +525,15 @@ export default class DataController {
|
|
|
523
525
|
*
|
|
524
526
|
* @event set
|
|
525
527
|
*/
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Event fired after the {@link #get get() method} has been run.
|
|
531
|
+
*
|
|
532
|
+
* The `get` event is fired by decorated {@link #get} method.
|
|
533
|
+
* See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
|
|
534
|
+
*
|
|
535
|
+
* @event get
|
|
536
|
+
*/
|
|
526
537
|
}
|
|
527
538
|
|
|
528
539
|
mix( DataController, ObservableMixin );
|
|
@@ -559,5 +570,43 @@ function _getMarkersRelativeToElement( element ) {
|
|
|
559
570
|
}
|
|
560
571
|
}
|
|
561
572
|
|
|
562
|
-
|
|
573
|
+
// Sort the markers in a stable fashion to ensure that the order in which they are
|
|
574
|
+
// added to the model's marker collection does not affect how they are
|
|
575
|
+
// downcast. One particular use case that we are targeting here, is one where
|
|
576
|
+
// two markers are adjacent but not overlapping, such as an insertion/deletion
|
|
577
|
+
// suggestion pair representing the replacement of a range of text. In this
|
|
578
|
+
// case, putting the markers in DOM order causes the first marker's end to be
|
|
579
|
+
// serialized right after the second marker's start, while putting the markers
|
|
580
|
+
// in reverse DOM order causes it to be right before the second marker's
|
|
581
|
+
// start. So, we sort these in a way that ensures non-intersecting ranges are in
|
|
582
|
+
// reverse DOM order, and intersecting ranges are in something approximating
|
|
583
|
+
// reverse DOM order (since reverse DOM order doesn't have a precise meaning
|
|
584
|
+
// when working with intersecting ranges).
|
|
585
|
+
return result.sort( ( [ n1, r1 ], [ n2, r2 ] ) => {
|
|
586
|
+
if ( r1.end.compareWith( r2.start ) !== 'after' ) {
|
|
587
|
+
// m1.end <= m2.start -- m1 is entirely <= m2
|
|
588
|
+
return 1;
|
|
589
|
+
} else if ( r1.start.compareWith( r2.end ) !== 'before' ) {
|
|
590
|
+
// m1.start >= m2.end -- m1 is entirely >= m2
|
|
591
|
+
return -1;
|
|
592
|
+
} else {
|
|
593
|
+
// they overlap, so use their start positions as the primary sort key and
|
|
594
|
+
// end positions as the secondary sort key
|
|
595
|
+
switch ( r1.start.compareWith( r2.start ) ) {
|
|
596
|
+
case 'before':
|
|
597
|
+
return 1;
|
|
598
|
+
case 'after':
|
|
599
|
+
return -1;
|
|
600
|
+
default:
|
|
601
|
+
switch ( r1.end.compareWith( r2.end ) ) {
|
|
602
|
+
case 'before':
|
|
603
|
+
return 1;
|
|
604
|
+
case 'after':
|
|
605
|
+
return -1;
|
|
606
|
+
default:
|
|
607
|
+
return n2.localeCompare( n1 );
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} );
|
|
563
612
|
}
|
|
@@ -28,26 +28,23 @@ export default class HtmlDataProcessor {
|
|
|
28
28
|
/**
|
|
29
29
|
* A DOM parser instance used to parse an HTML string to an HTML document.
|
|
30
30
|
*
|
|
31
|
-
* @private
|
|
32
31
|
* @member {DOMParser}
|
|
33
32
|
*/
|
|
34
|
-
this.
|
|
33
|
+
this.domParser = new DOMParser();
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
36
|
* A DOM converter used to convert DOM elements to view elements.
|
|
38
37
|
*
|
|
39
|
-
* @private
|
|
40
38
|
* @member {module:engine/view/domconverter~DomConverter}
|
|
41
39
|
*/
|
|
42
|
-
this.
|
|
40
|
+
this.domConverter = new DomConverter( document, { renderingMode: 'data' } );
|
|
43
41
|
|
|
44
42
|
/**
|
|
45
43
|
* A basic HTML writer instance used to convert DOM elements to an HTML string.
|
|
46
44
|
*
|
|
47
|
-
* @
|
|
48
|
-
* @member {module:engine/dataprocessor/basichtmlwriter~BasicHtmlWriter}
|
|
45
|
+
* @member {module:engine/dataprocessor/htmlwriter~HtmlWriter}
|
|
49
46
|
*/
|
|
50
|
-
this.
|
|
47
|
+
this.htmlWriter = new BasicHtmlWriter();
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|
|
@@ -59,10 +56,10 @@ export default class HtmlDataProcessor {
|
|
|
59
56
|
*/
|
|
60
57
|
toData( viewFragment ) {
|
|
61
58
|
// Convert view DocumentFragment to DOM DocumentFragment.
|
|
62
|
-
const domFragment = this.
|
|
59
|
+
const domFragment = this.domConverter.viewToDom( viewFragment, document );
|
|
63
60
|
|
|
64
61
|
// Convert DOM DocumentFragment to HTML output.
|
|
65
|
-
return this.
|
|
62
|
+
return this.htmlWriter.getHtml( domFragment );
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
/**
|
|
@@ -76,7 +73,7 @@ export default class HtmlDataProcessor {
|
|
|
76
73
|
const domFragment = this._toDom( data );
|
|
77
74
|
|
|
78
75
|
// Convert DOM DocumentFragment to view DocumentFragment.
|
|
79
|
-
return this.
|
|
76
|
+
return this.domConverter.domToView( domFragment );
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
/**
|
|
@@ -90,7 +87,7 @@ export default class HtmlDataProcessor {
|
|
|
90
87
|
* be treated as raw data.
|
|
91
88
|
*/
|
|
92
89
|
registerRawContentMatcher( pattern ) {
|
|
93
|
-
this.
|
|
90
|
+
this.domConverter.registerRawContentMatcher( pattern );
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
/**
|
|
@@ -105,7 +102,7 @@ export default class HtmlDataProcessor {
|
|
|
105
102
|
* @param {'default'|'marked'} type Whether to use the default or the marked ` ` block fillers.
|
|
106
103
|
*/
|
|
107
104
|
useFillerType( type ) {
|
|
108
|
-
this.
|
|
105
|
+
this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
|
|
109
106
|
}
|
|
110
107
|
|
|
111
108
|
/**
|
|
@@ -117,7 +114,7 @@ export default class HtmlDataProcessor {
|
|
|
117
114
|
* @returns {DocumentFragment}
|
|
118
115
|
*/
|
|
119
116
|
_toDom( data ) {
|
|
120
|
-
const document = this.
|
|
117
|
+
const document = this.domParser.parseFromString( data, 'text/html' );
|
|
121
118
|
const fragment = document.createDocumentFragment();
|
|
122
119
|
|
|
123
120
|
// The rules for parsing an HTML string can be read on https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhtml.
|
|
@@ -26,7 +26,7 @@ export default class XmlDataProcessor {
|
|
|
26
26
|
*
|
|
27
27
|
* @param {module:engine/view/document~Document} document The view document instance.
|
|
28
28
|
* @param {Object} options Configuration options.
|
|
29
|
-
* @param {Array
|
|
29
|
+
* @param {Array.<String>} [options.namespaces=[]] A list of namespaces allowed to use in the XML input.
|
|
30
30
|
*/
|
|
31
31
|
constructor( document, options = {} ) {
|
|
32
32
|
/**
|
|
@@ -35,35 +35,31 @@ export default class XmlDataProcessor {
|
|
|
35
35
|
* For example, registering namespaces [ 'attribute', 'container' ] allows to use `<attirbute:tagName></attribute:tagName>`
|
|
36
36
|
* and `<container:tagName></container:tagName>` input. It is mainly for debugging.
|
|
37
37
|
*
|
|
38
|
-
* @
|
|
39
|
-
* @member {DOMParser}
|
|
38
|
+
* @member {Array.<String>}
|
|
40
39
|
*/
|
|
41
40
|
this.namespaces = options.namespaces || [];
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
43
|
* DOM parser instance used to parse an XML string to an XML document.
|
|
45
44
|
*
|
|
46
|
-
* @private
|
|
47
45
|
* @member {DOMParser}
|
|
48
46
|
*/
|
|
49
|
-
this.
|
|
47
|
+
this.domParser = new DOMParser();
|
|
50
48
|
|
|
51
49
|
/**
|
|
52
50
|
* DOM converter used to convert DOM elements to view elements.
|
|
53
51
|
*
|
|
54
|
-
* @private
|
|
55
52
|
* @member {module:engine/view/domconverter~DomConverter}
|
|
56
53
|
*/
|
|
57
|
-
this.
|
|
54
|
+
this.domConverter = new DomConverter( document, { renderingMode: 'data' } );
|
|
58
55
|
|
|
59
56
|
/**
|
|
60
57
|
* A basic HTML writer instance used to convert DOM elements to an XML string.
|
|
61
58
|
* There is no need to use a dedicated XML writer because the basic HTML writer works well in this case.
|
|
62
59
|
*
|
|
63
|
-
* @
|
|
64
|
-
* @member {module:engine/dataprocessor/basichtmlwriter~BasicHtmlWriter}
|
|
60
|
+
* @member {module:engine/dataprocessor/htmlwriter~HtmlWriter}
|
|
65
61
|
*/
|
|
66
|
-
this.
|
|
62
|
+
this.htmlWriter = new BasicHtmlWriter();
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
/**
|
|
@@ -75,11 +71,11 @@ export default class XmlDataProcessor {
|
|
|
75
71
|
*/
|
|
76
72
|
toData( viewFragment ) {
|
|
77
73
|
// Convert view DocumentFragment to DOM DocumentFragment.
|
|
78
|
-
const domFragment = this.
|
|
74
|
+
const domFragment = this.domConverter.viewToDom( viewFragment, document );
|
|
79
75
|
|
|
80
76
|
// Convert DOM DocumentFragment to XML output.
|
|
81
77
|
// There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
|
|
82
|
-
return this.
|
|
78
|
+
return this.htmlWriter.getHtml( domFragment );
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
/**
|
|
@@ -93,7 +89,7 @@ export default class XmlDataProcessor {
|
|
|
93
89
|
const domFragment = this._toDom( data );
|
|
94
90
|
|
|
95
91
|
// Convert DOM DocumentFragment to view DocumentFragment.
|
|
96
|
-
return this.
|
|
92
|
+
return this.domConverter.domToView( domFragment, { keepOriginalCase: true } );
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
/**
|
|
@@ -107,7 +103,7 @@ export default class XmlDataProcessor {
|
|
|
107
103
|
* be treated as raw data.
|
|
108
104
|
*/
|
|
109
105
|
registerRawContentMatcher( pattern ) {
|
|
110
|
-
this.
|
|
106
|
+
this.domConverter.registerRawContentMatcher( pattern );
|
|
111
107
|
}
|
|
112
108
|
|
|
113
109
|
/**
|
|
@@ -122,7 +118,7 @@ export default class XmlDataProcessor {
|
|
|
122
118
|
* @param {'default'|'marked'} type Whether to use the default or the marked ` ` block fillers.
|
|
123
119
|
*/
|
|
124
120
|
useFillerType( type ) {
|
|
125
|
-
this.
|
|
121
|
+
this.domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
|
|
126
122
|
}
|
|
127
123
|
|
|
128
124
|
/**
|
|
@@ -140,7 +136,7 @@ export default class XmlDataProcessor {
|
|
|
140
136
|
// Wrap data into root element with optional namespace definitions.
|
|
141
137
|
data = `<xml ${ namespaces }>${ data }</xml>`;
|
|
142
138
|
|
|
143
|
-
const parsedDocument = this.
|
|
139
|
+
const parsedDocument = this.domParser.parseFromString( data, 'text/xml' );
|
|
144
140
|
|
|
145
141
|
// Parse validation.
|
|
146
142
|
const parserError = parsedDocument.querySelector( 'parsererror' );
|
package/src/dev-utils/view.js
CHANGED
|
@@ -39,6 +39,13 @@ const allowedTypes = {
|
|
|
39
39
|
'ui': UIElement,
|
|
40
40
|
'raw': RawElement
|
|
41
41
|
};
|
|
42
|
+
// Returns simplified implementation of {@link module:engine/view/domconverter~DomConverter#setContentOf DomConverter.setContentOf} method.
|
|
43
|
+
// Used to render UIElement and RawElement.
|
|
44
|
+
const domConverterStub = {
|
|
45
|
+
setContentOf: ( node, html ) => {
|
|
46
|
+
node.innerHTML = html;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
51
|
* Writes the content of the {@link module:engine/view/document~Document document} to an HTML-like string.
|
|
@@ -59,6 +66,9 @@ const allowedTypes = {
|
|
|
59
66
|
* {@link module:engine/view/uielement~UIElement} will be printed.
|
|
60
67
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
61
68
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
69
|
+
* @param {Object} [options.domConverter=null] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
70
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
71
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
62
72
|
* @returns {String} The stringified data.
|
|
63
73
|
*/
|
|
64
74
|
export function getData( view, options = {} ) {
|
|
@@ -75,7 +85,8 @@ export function getData( view, options = {} ) {
|
|
|
75
85
|
showPriority: options.showPriority,
|
|
76
86
|
renderUIElements: options.renderUIElements,
|
|
77
87
|
renderRawElements: options.renderRawElements,
|
|
78
|
-
ignoreRoot: true
|
|
88
|
+
ignoreRoot: true,
|
|
89
|
+
domConverter: options.domConverter
|
|
79
90
|
};
|
|
80
91
|
|
|
81
92
|
return withoutSelection ?
|
|
@@ -241,6 +252,9 @@ setData._parse = parse;
|
|
|
241
252
|
* {@link module:engine/view/uielement~UIElement} will be printed.
|
|
242
253
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
243
254
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
255
|
+
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
256
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
257
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
244
258
|
* @returns {String} An HTML-like string representing the view.
|
|
245
259
|
*/
|
|
246
260
|
export function stringify( node, selectionOrPositionOrRange = null, options = {} ) {
|
|
@@ -630,6 +644,9 @@ class ViewStringify {
|
|
|
630
644
|
* @param {Boolean} [options.renderUIElements=false] When set to `true`, the inner content of each
|
|
631
645
|
* {@link module:engine/view/uielement~UIElement} will be printed.
|
|
632
646
|
* @param {Boolean} [options.renderRawElements=false] When set to `true`, the inner content of each
|
|
647
|
+
* @param {Object} [options.domConverter={}] When set to an actual {@link module:engine/view/domconverter~DomConverter DomConverter}
|
|
648
|
+
* instance, it lets the conversion go through exactly the same flow the editing view is going through,
|
|
649
|
+
* i.e. with view data filtering. Otherwise the simple stub is used.
|
|
633
650
|
* {@link module:engine/view/rawelement~RawElement} will be printed.
|
|
634
651
|
*/
|
|
635
652
|
constructor( root, selection, options ) {
|
|
@@ -648,6 +665,7 @@ class ViewStringify {
|
|
|
648
665
|
this.sameSelectionCharacters = !!options.sameSelectionCharacters;
|
|
649
666
|
this.renderUIElements = !!options.renderUIElements;
|
|
650
667
|
this.renderRawElements = !!options.renderRawElements;
|
|
668
|
+
this.domConverter = options.domConverter || domConverterStub;
|
|
651
669
|
}
|
|
652
670
|
|
|
653
671
|
/**
|
|
@@ -681,12 +699,12 @@ class ViewStringify {
|
|
|
681
699
|
}
|
|
682
700
|
|
|
683
701
|
if ( ( this.renderUIElements && root.is( 'uiElement' ) ) ) {
|
|
684
|
-
callback( root.render( document ).innerHTML );
|
|
702
|
+
callback( root.render( document, this.domConverter ).innerHTML );
|
|
685
703
|
} else if ( this.renderRawElements && root.is( 'rawElement' ) ) {
|
|
686
704
|
// There's no DOM element for "root" to pass to render(). Creating
|
|
687
705
|
// a surrogate container to render the children instead.
|
|
688
706
|
const rawContentContainer = document.createElement( 'div' );
|
|
689
|
-
root.render( rawContentContainer );
|
|
707
|
+
root.render( rawContentContainer, this.domConverter );
|
|
690
708
|
|
|
691
709
|
callback( rawContentContainer.innerHTML );
|
|
692
710
|
} else {
|
|
@@ -52,12 +52,13 @@ export default class MarkerCollection {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Checks if marker
|
|
55
|
+
* Checks if given {@link ~Marker marker} or marker name is in the collection.
|
|
56
56
|
*
|
|
57
|
-
* @param {String}
|
|
58
|
-
* @returns {Boolean} `true` if marker
|
|
57
|
+
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker or marker instance to check.
|
|
58
|
+
* @returns {Boolean} `true` if marker is in the collection, `false` otherwise.
|
|
59
59
|
*/
|
|
60
|
-
has(
|
|
60
|
+
has( markerOrName ) {
|
|
61
|
+
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
|
|
61
62
|
return this._markers.has( markerName );
|
|
62
63
|
}
|
|
63
64
|
|
package/src/model/range.js
CHANGED
|
@@ -446,7 +446,7 @@ export default class Range {
|
|
|
446
446
|
* You may specify additional options for the tree walker. See {@link module:engine/model/treewalker~TreeWalker} for
|
|
447
447
|
* a full list of available options.
|
|
448
448
|
*
|
|
449
|
-
* @param {Object} options Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
|
|
449
|
+
* @param {Object} [options] Object with configuration options. See {@link module:engine/model/treewalker~TreeWalker}.
|
|
450
450
|
* @returns {Iterable.<module:engine/model/item~Item>}
|
|
451
451
|
*/
|
|
452
452
|
* getItems( options = {} ) {
|
package/src/model/selection.js
CHANGED
|
@@ -71,7 +71,7 @@ export default class Selection {
|
|
|
71
71
|
* // Creates backward selection.
|
|
72
72
|
* const selection = writer.createSelection( range, { backward: true } );
|
|
73
73
|
*
|
|
74
|
-
* @param {module:engine/model/selection~Selectable} selectable
|
|
74
|
+
* @param {module:engine/model/selection~Selectable} [selectable]
|
|
75
75
|
* @param {Number|'before'|'end'|'after'|'on'|'in'} [placeOrOffset] Sets place or offset of the selection.
|
|
76
76
|
* @param {Object} [options]
|
|
77
77
|
* @param {Boolean} [options.backward] Sets this selection instance to be backward.
|
|
@@ -128,9 +128,17 @@ function tryFixingCollapsedRange( range, schema ) {
|
|
|
128
128
|
|
|
129
129
|
const nearestSelectionRange = schema.getNearestSelectionRange( originalPosition );
|
|
130
130
|
|
|
131
|
-
// This might be null
|
|
132
|
-
//
|
|
131
|
+
// This might be null, i.e. when the editor data is empty or the selection is inside a limit element
|
|
132
|
+
// that doesn't allow text inside.
|
|
133
|
+
// In the first case, there is no need to fix the selection range.
|
|
134
|
+
// In the second, let's go up to the outer selectable element
|
|
133
135
|
if ( !nearestSelectionRange ) {
|
|
136
|
+
const ancestorObject = originalPosition.getAncestors().reverse().find( item => schema.isObject( item ) );
|
|
137
|
+
|
|
138
|
+
if ( ancestorObject ) {
|
|
139
|
+
return Range._createOn( ancestorObject );
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
return null;
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -255,35 +263,42 @@ function checkSelectionOnNonLimitElements( start, end, schema ) {
|
|
|
255
263
|
return startIsOnBlock || endIsOnBlock;
|
|
256
264
|
}
|
|
257
265
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Returns a minimal non-intersecting array of ranges without duplicates.
|
|
268
|
+
*
|
|
269
|
+
* @param {Array.<module:engine/model/range~Range>} Ranges to merge.
|
|
270
|
+
* @returns {Array.<module:engine/model/range~Range>} Array of unique and nonIntersecting ranges.
|
|
271
|
+
*/
|
|
272
|
+
export function mergeIntersectingRanges( ranges ) {
|
|
273
|
+
const rangesToMerge = [ ...ranges ];
|
|
274
|
+
const rangeIndexesToRemove = new Set();
|
|
275
|
+
let currentRangeIndex = 1;
|
|
276
|
+
|
|
277
|
+
while ( currentRangeIndex < rangesToMerge.length ) {
|
|
278
|
+
const currentRange = rangesToMerge[ currentRangeIndex ];
|
|
279
|
+
const previousRanges = rangesToMerge.slice( 0, currentRangeIndex );
|
|
280
|
+
|
|
281
|
+
for ( const [ previousRangeIndex, previousRange ] of previousRanges.entries() ) {
|
|
282
|
+
if ( rangeIndexesToRemove.has( previousRangeIndex ) ) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if ( currentRange.isEqual( previousRange ) ) {
|
|
287
|
+
rangeIndexesToRemove.add( previousRangeIndex );
|
|
288
|
+
} else if ( currentRange.isIntersecting( previousRange ) ) {
|
|
289
|
+
rangeIndexesToRemove.add( previousRangeIndex );
|
|
290
|
+
rangeIndexesToRemove.add( currentRangeIndex );
|
|
291
|
+
|
|
292
|
+
const mergedRange = currentRange.getJoined( previousRange );
|
|
293
|
+
rangesToMerge.push( mergedRange );
|
|
294
|
+
}
|
|
284
295
|
}
|
|
296
|
+
|
|
297
|
+
currentRangeIndex++;
|
|
285
298
|
}
|
|
286
299
|
|
|
300
|
+
const nonIntersectingRanges = rangesToMerge.filter( ( _, index ) => !rangeIndexesToRemove.has( index ) );
|
|
301
|
+
|
|
287
302
|
return nonIntersectingRanges;
|
|
288
303
|
}
|
|
289
304
|
|
package/src/view/document.js
CHANGED
|
@@ -80,6 +80,18 @@ export default class Document {
|
|
|
80
80
|
*/
|
|
81
81
|
this.set( 'isFocused', false );
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* `true` while the user is making a selection in the document (e.g. holding the mouse button and moving the cursor).
|
|
85
|
+
* When they stop selecting, the property goes back to `false`.
|
|
86
|
+
*
|
|
87
|
+
* This property is updated by the {@link module:engine/view/observer/selectionobserver~SelectionObserver}.
|
|
88
|
+
*
|
|
89
|
+
* @readonly
|
|
90
|
+
* @observable
|
|
91
|
+
* @member {Boolean} module:engine/view/document~Document#isSelecting
|
|
92
|
+
*/
|
|
93
|
+
this.set( 'isSelecting', false );
|
|
94
|
+
|
|
83
95
|
/**
|
|
84
96
|
* True if composition is in progress inside the document.
|
|
85
97
|
*
|