@ckeditor/ckeditor5-typing 40.1.0 → 40.2.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/CHANGELOG.md CHANGED
@@ -5,12 +5,12 @@ All changes in the package are documented in the main repository. See: https://g
5
5
 
6
6
  Changes for the past releases are available below.
7
7
 
8
- ## [19.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v18.0.0...v19.0.0) (2020-04-29)
8
+ ## [19.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v18.0.0...v19.0.0) (April 29, 2020)
9
9
 
10
10
  Internal changes only (updated dependencies, documentation, etc.).
11
11
 
12
12
 
13
- ## [18.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v17.0.0...v18.0.0) (2020-03-19)
13
+ ## [18.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v17.0.0...v18.0.0) (March 19, 2020)
14
14
 
15
15
  ### Other changes
16
16
 
@@ -19,26 +19,26 @@ Internal changes only (updated dependencies, documentation, etc.).
19
19
  * Run only one instance of the `TextWatcher` for all text transformations. Closes [ckeditor/ckeditor5#6020](https://github.com/ckeditor/ckeditor5/issues/6020). ([550426d](https://github.com/ckeditor/ckeditor5-typing/commit/550426d))
20
20
 
21
21
 
22
- ## [17.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v16.0.0...v17.0.0) (2020-02-19)
22
+ ## [17.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v16.0.0...v17.0.0) (February 19, 2020)
23
23
 
24
24
  ### Features
25
25
 
26
26
  * Add `TextWatcher#isEnabled` property to allow toggling text watcher on and off. ([fa79d00](https://github.com/ckeditor/ckeditor5-typing/commit/fa79d00))
27
27
 
28
28
 
29
- ## [16.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v15.0.0...v16.0.0) (2019-12-04)
29
+ ## [16.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v15.0.0...v16.0.0) (December 4, 2019)
30
30
 
31
31
  Internal changes only (updated dependencies, documentation, etc.).
32
32
 
33
33
 
34
- ## [15.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.2.0...v15.0.0) (2019-10-23)
34
+ ## [15.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.2.0...v15.0.0) (October 23, 2019)
35
35
 
36
36
  ### Bug fixes
37
37
 
38
38
  * Autoformat transformations in blocks containing inline elements. See [ckeditor/ckeditor5#1955](https://github.com/ckeditor/ckeditor5/issues/1955). ([58abd23](https://github.com/ckeditor/ckeditor5-typing/commit/58abd23))
39
39
 
40
40
 
41
- ## [12.2.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.1.1...v12.2.0) (2019-08-26)
41
+ ## [12.2.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.1.1...v12.2.0) (August 26, 2019)
42
42
 
43
43
  ### Features
44
44
 
@@ -54,14 +54,14 @@ Internal changes only (updated dependencies, documentation, etc.).
54
54
  * The issue tracker for this package was moved to https://github.com/ckeditor/ckeditor5/issues. See [ckeditor/ckeditor5#1988](https://github.com/ckeditor/ckeditor5/issues/1988). ([6491e8d](https://github.com/ckeditor/ckeditor5-typing/commit/6491e8d))
55
55
 
56
56
 
57
- ## [12.1.1](https://github.com/ckeditor/ckeditor5-typing/compare/v12.1.0...v12.1.1) (2019-07-10)
57
+ ## [12.1.1](https://github.com/ckeditor/ckeditor5-typing/compare/v12.1.0...v12.1.1) (July 10, 2019)
58
58
 
59
59
  ### Bug fixes
60
60
 
61
61
  * Text transformations will not remove existing formatting. Closes [#203](https://github.com/ckeditor/ckeditor5-typing/issues/203). Closes [#196](https://github.com/ckeditor/ckeditor5-typing/issues/196). ([2279eee](https://github.com/ckeditor/ckeditor5-typing/commit/2279eee))
62
62
 
63
63
 
64
- ## [12.1.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.2...v12.1.0) (2019-07-04)
64
+ ## [12.1.0](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.2...v12.1.0) (July 4, 2019)
65
65
 
66
66
  ### Features
67
67
 
@@ -72,21 +72,21 @@ Internal changes only (updated dependencies, documentation, etc.).
72
72
  * Improved typing on Android devices by handling `beforeinput` event instead of mutations. Introduced `options.selection` in the `DeleteCommand#execute()` params. Introduced `selectionToRemove` parameter in the `view.Document#event:delete` data. Closes [#167](https://github.com/ckeditor/ckeditor5-typing/issues/167). ([92ab3ff](https://github.com/ckeditor/ckeditor5-typing/commit/92ab3ff))
73
73
 
74
74
 
75
- ## [12.0.2](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.1...v12.0.2) (2019-06-05)
75
+ ## [12.0.2](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.1...v12.0.2) (June 6, 2019)
76
76
 
77
77
  ### Other changes
78
78
 
79
79
  * Use `Model#insertContent()` instead of `model.Writer#insertText()`. Closes [#191](https://github.com/ckeditor/ckeditor5-typing/issues/191). ([0aeb384](https://github.com/ckeditor/ckeditor5-typing/commit/0aeb384))
80
80
 
81
81
 
82
- ## [12.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.0...v12.0.1) (2019-04-10)
82
+ ## [12.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v12.0.0...v12.0.1) (April 4, 2019)
83
83
 
84
84
  ### Bug fixes
85
85
 
86
86
  * The `delete` event will now stop the `keydown` event if it was set with the highest priority. Closes [#186](https://github.com/ckeditor/ckeditor5-typing/issues/186). ([07cca83](https://github.com/ckeditor/ckeditor5-typing/commit/07cca83))
87
87
 
88
88
 
89
- ## [12.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.2...v12.0.0) (2019-02-28)
89
+ ## [12.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.2...v12.0.0) (February 28, 2019)
90
90
 
91
91
  ### Bug fixes
92
92
 
@@ -101,21 +101,21 @@ Internal changes only (updated dependencies, documentation, etc.).
101
101
  * Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c))
102
102
 
103
103
 
104
- ## [11.0.2](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.1...v11.0.2) (2018-12-05)
104
+ ## [11.0.2](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.1...v11.0.2) (December 5, 2018)
105
105
 
106
106
  ### Bug fixes
107
107
 
108
108
  * Non-printable keys like volume up or the win key will not remove the content anymore. Closes [#136](https://github.com/ckeditor/ckeditor5-typing/issues/136). ([0ea9fbd](https://github.com/ckeditor/ckeditor5-typing/commit/0ea9fbd))
109
109
 
110
110
 
111
- ## [11.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.0...v11.0.1) (2018-10-08)
111
+ ## [11.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v11.0.0...v11.0.1) (October 8, 2018)
112
112
 
113
113
  ### Bug fixes
114
114
 
115
115
  * ` ` is now correctly handled in mutations. Closes [#170](https://github.com/ckeditor/ckeditor5-typing/issues/170). ([9badb20](https://github.com/ckeditor/ckeditor5-typing/commit/9badb20))
116
116
 
117
117
 
118
- ## [11.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v10.0.1...v11.0.0) (2018-07-18)
118
+ ## [11.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v10.0.1...v11.0.0) (July 18, 2018)
119
119
 
120
120
  ### Bug fixes
121
121
 
@@ -127,14 +127,14 @@ Internal changes only (updated dependencies, documentation, etc.).
127
127
  * `@ckeditor/ckeditor5-typing/src/changebuffer.js` was moved to `@ckeditor/ckeditor5-typing/src/utils/changebuffer.js`.
128
128
 
129
129
 
130
- ## [10.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v10.0.0...v10.0.1) (2018-06-21)
130
+ ## [10.0.1](https://github.com/ckeditor/ckeditor5-typing/compare/v10.0.0...v10.0.1) (June 21, 2018)
131
131
 
132
132
  ### Bug fixes
133
133
 
134
134
  * Bogus `<br />` element inserted by a browser at the end of an element is now correctly handled. Closes [ckeditor/ckeditor5#1083](https://github.com/ckeditor/ckeditor5/issues/1083). ([22abdff](https://github.com/ckeditor/ckeditor5-typing/commit/22abdff))
135
135
 
136
136
 
137
- ## [10.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.4...v10.0.0) (2018-04-25)
137
+ ## [10.0.0](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.4...v10.0.0) (April 25, 2018)
138
138
 
139
139
  ### Other changes
140
140
 
@@ -145,17 +145,17 @@ Internal changes only (updated dependencies, documentation, etc.).
145
145
  * The license under which CKEditor&nbsp;5 is released has been changed from a triple GPL, LGPL and MPL license to a GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991) for more information.
146
146
 
147
147
 
148
- ## [1.0.0-beta.4](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.2...v1.0.0-beta.4) (2018-04-19)
148
+ ## [1.0.0-beta.4](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.2...v1.0.0-beta.4) (April 19, 2018)
149
149
 
150
150
  Internal changes only (updated dependencies, documentation, etc.).
151
151
 
152
152
 
153
- ## [1.0.0-beta.2](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2018-04-10)
153
+ ## [1.0.0-beta.2](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-beta.1...v1.0.0-beta.2) (April 10, 2018)
154
154
 
155
155
  Internal changes only (updated dependencies, documentation, etc.).
156
156
 
157
157
 
158
- ## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (2018-03-15)
158
+ ## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (March 15, 2018)
159
159
 
160
160
  ### Bug fixes
161
161
 
@@ -168,11 +168,11 @@ Internal changes only (updated dependencies, documentation, etc.).
168
168
  * Aligned feature class naming to the new scheme. ([9c2cb9d](https://github.com/ckeditor/ckeditor5-typing/commit/9c2cb9d))
169
169
 
170
170
 
171
- ## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14)
171
+ ## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-typing/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (November 14, 2017)
172
172
 
173
173
  Internal changes only (updated dependencies, documentation, etc.).
174
174
 
175
- ## [1.0.0-alpha.1](https://github.com/ckeditor/ckeditor5-typing/compare/v0.10.0...v1.0.0-alpha.1) (2017-10-03)
175
+ ## [1.0.0-alpha.1](https://github.com/ckeditor/ckeditor5-typing/compare/v0.10.0...v1.0.0-alpha.1) (October 3, 2017)
176
176
 
177
177
  ### Bug fixes
178
178
 
@@ -180,7 +180,7 @@ Internal changes only (updated dependencies, documentation, etc.).
180
180
  * Fixed an error where using spellchecker on a word with a style applied sometimes resulted in that word being removed. Closes [#117](https://github.com/ckeditor/ckeditor5-typing/issues/117). ([1e8d02b](https://github.com/ckeditor/ckeditor5-typing/commit/1e8d02b))
181
181
 
182
182
 
183
- ## [0.10.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.9.1...v0.10.0) (2017-09-03)
183
+ ## [0.10.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.9.1...v0.10.0) (September 3, 2017)
184
184
 
185
185
  ### Bug fixes
186
186
 
@@ -201,17 +201,17 @@ Internal changes only (updated dependencies, documentation, etc.).
201
201
  * The command API has been changed.
202
202
 
203
203
 
204
- ## [0.9.1](https://github.com/ckeditor/ckeditor5-typing/compare/v0.9.0...v0.9.1) (2017-05-07)
204
+ ## [0.9.1](https://github.com/ckeditor/ckeditor5-typing/compare/v0.9.0...v0.9.1) (May 7, 2017)
205
205
 
206
206
  Internal changes only (updated dependencies, documentation, etc.).
207
207
 
208
- ## [0.9.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.8.0...v0.9.0) (2017-04-05)
208
+ ## [0.9.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.8.0...v0.9.0) (April 5, 2017)
209
209
 
210
210
  ### Bug fixes
211
211
 
212
- * [Safari] Fixed an issue when inserting a Spanish accent character on a non-collapsed selection wouldn't work. Closes [#82](https://github.com/ckeditor/ckeditor5-typing/issues/82). ([49cfe9c](https://github.com/ckeditor/ckeditor5-typing/commit/49cfe9c))
212
+ * [Safari] Fixed an issue when inserting a Spanish accent character on a non-collapsed selection would not work. Closes [#82](https://github.com/ckeditor/ckeditor5-typing/issues/82). ([49cfe9c](https://github.com/ckeditor/ckeditor5-typing/commit/49cfe9c))
213
213
  * `InputCommand` now accepts `Range` instead of `Position` as a parameter. Closes [#86](https://github.com/ckeditor/ckeditor5-typing/issues/86). Closes [#54](https://github.com/ckeditor/ckeditor5-typing/issues/54). ([0766407](https://github.com/ckeditor/ckeditor5-typing/commit/0766407))
214
- * New undo step should be created on selection change or applying an attribute. Closes [#20](https://github.com/ckeditor/ckeditor5-typing/issues/20). Closes [#21](https://github.com/ckeditor/ckeditor5-typing/issues/21). ([011452b](https://github.com/ckeditor/ckeditor5-typing/commit/011452b))
214
+ * A new undo step should be created on selection change or applying an attribute. Closes [#20](https://github.com/ckeditor/ckeditor5-typing/issues/20). Closes [#21](https://github.com/ckeditor/ckeditor5-typing/issues/21). ([011452b](https://github.com/ckeditor/ckeditor5-typing/commit/011452b))
215
215
  * Use `typing.undoStep` in both `InputCommand` and `DeleteCommand`. Closes [#79](https://github.com/ckeditor/ckeditor5-typing/issues/79). ([c597467](https://github.com/ckeditor/ckeditor5-typing/commit/c597467))
216
216
 
217
217
  ### Features
@@ -224,7 +224,7 @@ Internal changes only (updated dependencies, documentation, etc.).
224
224
  * The `undo.step` configuration option was replaced by `typing.undoStep` in `DeleteCommand`. See [#79](https://github.com/ckeditor/ckeditor5-typing/issues/79).
225
225
 
226
226
 
227
- ## [0.8.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.7.0...v0.8.0) (2017-03-06)
227
+ ## [0.8.0](https://github.com/ckeditor/ckeditor5-typing/compare/v0.7.0...v0.8.0) (March 6, 2017)
228
228
 
229
229
  ### Bug fixes
230
230
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-typing",
3
- "version": "40.1.0",
3
+ "version": "40.2.0",
4
4
  "description": "Typing feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -12,9 +12,9 @@
12
12
  ],
13
13
  "main": "src/index.js",
14
14
  "dependencies": {
15
- "@ckeditor/ckeditor5-core": "40.1.0",
16
- "@ckeditor/ckeditor5-engine": "40.1.0",
17
- "@ckeditor/ckeditor5-utils": "40.1.0",
15
+ "@ckeditor/ckeditor5-core": "40.2.0",
16
+ "@ckeditor/ckeditor5-engine": "40.2.0",
17
+ "@ckeditor/ckeditor5-utils": "40.2.0",
18
18
  "lodash-es": "4.17.21"
19
19
  },
20
20
  "author": "CKSource (http://cksource.com/)",
@@ -63,9 +63,12 @@ export default class InsertTextCommand extends Command {
63
63
  const resultRange = options.resultRange;
64
64
  model.enqueueChange(this._buffer.batch, writer => {
65
65
  this._buffer.lock();
66
+ // Store selection attributes before deleting old content to preserve formatting and link.
67
+ // This unifies the behavior between DocumentSelection and Selection provided as input option.
68
+ const selectionAttributes = Array.from(doc.selection.getAttributes());
66
69
  model.deleteContent(selection);
67
70
  if (text) {
68
- model.insertContent(writer.createText(text, doc.selection.getAttributes()), selection);
71
+ model.insertContent(writer.createText(text, selectionAttributes), selection);
69
72
  }
70
73
  if (resultRange) {
71
74
  writer.setSelection(resultRange);
@@ -179,6 +179,39 @@ export default class TwoStepCaretMovement extends Plugin {
179
179
  * @returns `true` when the handler prevented caret movement
180
180
  */
181
181
  private _handleBackwardMovement;
182
+ /**
183
+ * Starts listening to {@link module:engine/view/document~Document#event:mousedown} and
184
+ * {@link module:engine/view/document~Document#event:selectionChange} and puts the selection before/after a 2-step node
185
+ * if clicked at the beginning/ending of the 2-step node.
186
+ *
187
+ * The purpose of this action is to allow typing around the 2-step node directly after a click.
188
+ *
189
+ * See https://github.com/ckeditor/ckeditor5/issues/1016.
190
+ */
191
+ private _enableClickingAfterNode;
192
+ /**
193
+ * Starts listening to {@link module:engine/model/model~Model#event:insertContent} and corrects the model
194
+ * selection attributes if the selection is at the end of a two-step node after inserting the content.
195
+ *
196
+ * The purpose of this action is to improve the overall UX because the user is no longer "trapped" by the
197
+ * two-step attribute of the selection, and they can type a "clean" (`linkHref`–less) text right away.
198
+ *
199
+ * See https://github.com/ckeditor/ckeditor5/issues/6053.
200
+ */
201
+ private _enableInsertContentSelectionAttributesFixer;
202
+ /**
203
+ * Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether
204
+ * removing a content right after the tow-step attribute.
205
+ *
206
+ * If so, the selection should not preserve the two-step attribute. However, if
207
+ * the {@link module:typing/twostepcaretmovement~TwoStepCaretMovement} plugin is active and
208
+ * the selection has the two-step attribute due to overridden gravity (at the end), the two-step attribute should stay untouched.
209
+ *
210
+ * The purpose of this action is to allow removing the link text and keep the selection outside the link.
211
+ *
212
+ * See https://github.com/ckeditor/ckeditor5/issues/7521.
213
+ */
214
+ private _handleDeleteContentAfterNode;
182
215
  /**
183
216
  * `true` when the gravity is overridden for the plugin.
184
217
  */
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { Plugin } from '@ckeditor/ckeditor5-core';
9
9
  import { keyCodes } from '@ckeditor/ckeditor5-utils';
10
+ import { MouseObserver } from '@ckeditor/ckeditor5-engine';
10
11
  /**
11
12
  * This plugin enables the two-step caret (phantom) movement behavior for
12
13
  * {@link module:typing/twostepcaretmovement~TwoStepCaretMovement#registerAttribute registered attributes}
@@ -142,6 +143,12 @@ export default class TwoStepCaretMovement extends Plugin {
142
143
  */
143
144
  constructor(editor) {
144
145
  super(editor);
146
+ /**
147
+ * A flag indicating that the automatic gravity restoration should not happen upon the next
148
+ * gravity restoration.
149
+ * {@link module:engine/model/selection~Selection#event:change:range} event.
150
+ */
151
+ this._isNextGravityRestorationSkipped = false;
145
152
  this.attributes = new Set();
146
153
  this._overrideUid = null;
147
154
  }
@@ -185,7 +192,6 @@ export default class TwoStepCaretMovement extends Plugin {
185
192
  evt.stop();
186
193
  }
187
194
  }, { context: '$text', priority: 'highest' });
188
- this._isNextGravityRestorationSkipped = false;
189
195
  // The automatic gravity restoration logic.
190
196
  this.listenTo(modelSelection, 'change:range', (evt, data) => {
191
197
  // Skipping the automatic restoration is needed if the selection should change
@@ -208,6 +214,12 @@ export default class TwoStepCaretMovement extends Plugin {
208
214
  }
209
215
  this._restoreGravity();
210
216
  });
217
+ // Handle a click at the beginning/end of a two-step element.
218
+ this._enableClickingAfterNode();
219
+ // Change the attributes of the selection in certain situations after the two-step node was inserted into the document.
220
+ this._enableInsertContentSelectionAttributesFixer();
221
+ // Handle removing the content after the two-step node.
222
+ this._handleDeleteContentAfterNode();
211
223
  }
212
224
  /**
213
225
  * Registers a given attribute for the two-step caret movement.
@@ -260,7 +272,18 @@ export default class TwoStepCaretMovement extends Plugin {
260
272
  //
261
273
  if (isBetweenDifferentAttributes(position, attributes)) {
262
274
  preventCaretMovement(data);
263
- this._overrideGravity();
275
+ // CLEAR 2-SCM attributes if we are at the end of one 2-SCM and before
276
+ // the next one with a different value of the same attribute.
277
+ //
278
+ // <paragraph>foo<$text attribute=1>bar{}</$text><$text attribute=2>bar</$text>baz</paragraph>
279
+ //
280
+ if (hasAnyAttribute(selection, attributes) &&
281
+ isBetweenDifferentAttributes(position, attributes, true)) {
282
+ clearSelectionAttributes(model, attributes);
283
+ }
284
+ else {
285
+ this._overrideGravity();
286
+ }
264
287
  return true;
265
288
  }
266
289
  return false;
@@ -288,7 +311,17 @@ export default class TwoStepCaretMovement extends Plugin {
288
311
  if (this._isGravityOverridden) {
289
312
  preventCaretMovement(data);
290
313
  this._restoreGravity();
291
- setSelectionAttributesFromTheNodeBefore(model, attributes, position);
314
+ // CLEAR 2-SCM attributes if we are at the end of one 2-SCM and before
315
+ // the next one with a different value of the same attribute.
316
+ //
317
+ // <paragraph>foo<$text attribute=1>bar</$text><$text attribute=2>{}bar</$text>baz</paragraph>
318
+ //
319
+ if (isBetweenDifferentAttributes(position, attributes, true)) {
320
+ clearSelectionAttributes(model, attributes);
321
+ }
322
+ else {
323
+ setSelectionAttributesFromTheNodeBefore(model, attributes, position);
324
+ }
292
325
  return true;
293
326
  }
294
327
  else {
@@ -305,6 +338,16 @@ export default class TwoStepCaretMovement extends Plugin {
305
338
  }
306
339
  return false;
307
340
  }
341
+ // SET 2-SCM attributes if we are between nodes with the same attribute but with different values.
342
+ //
343
+ // <paragraph>foo<$text attribute=1>bar</$text>[]<$text attribute=2>bar</$text>baz</paragraph>
344
+ //
345
+ if (!hasAnyAttribute(selection, attributes) &&
346
+ isBetweenDifferentAttributes(position, attributes, true)) {
347
+ preventCaretMovement(data);
348
+ setSelectionAttributesFromTheNodeBefore(model, attributes, position);
349
+ return true;
350
+ }
308
351
  // When we are moving from natural gravity, to the position of the 2SCM, we need to override the gravity,
309
352
  // and make sure it won't be restored. Unless it's at the end of the block and an observed attribute.
310
353
  // We need to check if the caret is a one position before the attribute boundary:
@@ -340,6 +383,139 @@ export default class TwoStepCaretMovement extends Plugin {
340
383
  }
341
384
  return false;
342
385
  }
386
+ /**
387
+ * Starts listening to {@link module:engine/view/document~Document#event:mousedown} and
388
+ * {@link module:engine/view/document~Document#event:selectionChange} and puts the selection before/after a 2-step node
389
+ * if clicked at the beginning/ending of the 2-step node.
390
+ *
391
+ * The purpose of this action is to allow typing around the 2-step node directly after a click.
392
+ *
393
+ * See https://github.com/ckeditor/ckeditor5/issues/1016.
394
+ */
395
+ _enableClickingAfterNode() {
396
+ const editor = this.editor;
397
+ const model = editor.model;
398
+ const selection = model.document.selection;
399
+ const document = editor.editing.view.document;
400
+ editor.editing.view.addObserver(MouseObserver);
401
+ let clicked = false;
402
+ // Detect the click.
403
+ this.listenTo(document, 'mousedown', () => {
404
+ clicked = true;
405
+ });
406
+ // When the selection has changed...
407
+ this.listenTo(document, 'selectionChange', () => {
408
+ const attributes = this.attributes;
409
+ if (!clicked) {
410
+ return;
411
+ }
412
+ // ...and it was caused by the click...
413
+ clicked = false;
414
+ // ...and no text is selected...
415
+ if (!selection.isCollapsed) {
416
+ return;
417
+ }
418
+ // ...and clicked text is the 2-step node...
419
+ if (!hasAnyAttribute(selection, attributes)) {
420
+ return;
421
+ }
422
+ const position = selection.getFirstPosition();
423
+ if (!isBetweenDifferentAttributes(position, attributes)) {
424
+ return;
425
+ }
426
+ // The selection at the start of a block would use surrounding attributes
427
+ // from text after the selection so just clear 2-SCM attributes.
428
+ //
429
+ // Also, clear attributes for selection between same attribute with different values.
430
+ if (position.isAtStart ||
431
+ isBetweenDifferentAttributes(position, attributes, true)) {
432
+ clearSelectionAttributes(model, attributes);
433
+ }
434
+ else if (!this._isGravityOverridden) {
435
+ this._overrideGravity();
436
+ }
437
+ });
438
+ }
439
+ /**
440
+ * Starts listening to {@link module:engine/model/model~Model#event:insertContent} and corrects the model
441
+ * selection attributes if the selection is at the end of a two-step node after inserting the content.
442
+ *
443
+ * The purpose of this action is to improve the overall UX because the user is no longer "trapped" by the
444
+ * two-step attribute of the selection, and they can type a "clean" (`linkHref`–less) text right away.
445
+ *
446
+ * See https://github.com/ckeditor/ckeditor5/issues/6053.
447
+ */
448
+ _enableInsertContentSelectionAttributesFixer() {
449
+ const editor = this.editor;
450
+ const model = editor.model;
451
+ const selection = model.document.selection;
452
+ const attributes = this.attributes;
453
+ this.listenTo(model, 'insertContent', () => {
454
+ const position = selection.getFirstPosition();
455
+ if (hasAnyAttribute(selection, attributes) &&
456
+ isBetweenDifferentAttributes(position, attributes)) {
457
+ clearSelectionAttributes(model, attributes);
458
+ }
459
+ }, { priority: 'low' });
460
+ }
461
+ /**
462
+ * Starts listening to {@link module:engine/model/model~Model#deleteContent} and checks whether
463
+ * removing a content right after the tow-step attribute.
464
+ *
465
+ * If so, the selection should not preserve the two-step attribute. However, if
466
+ * the {@link module:typing/twostepcaretmovement~TwoStepCaretMovement} plugin is active and
467
+ * the selection has the two-step attribute due to overridden gravity (at the end), the two-step attribute should stay untouched.
468
+ *
469
+ * The purpose of this action is to allow removing the link text and keep the selection outside the link.
470
+ *
471
+ * See https://github.com/ckeditor/ckeditor5/issues/7521.
472
+ */
473
+ _handleDeleteContentAfterNode() {
474
+ const editor = this.editor;
475
+ const model = editor.model;
476
+ const selection = model.document.selection;
477
+ const view = editor.editing.view;
478
+ let isBackspace = false;
479
+ let shouldPreserveAttributes = false;
480
+ // Detect pressing `Backspace`.
481
+ this.listenTo(view.document, 'delete', (evt, data) => {
482
+ isBackspace = data.direction === 'backward';
483
+ }, { priority: 'high' });
484
+ // Before removing the content, check whether the selection is inside a two-step attribute.
485
+ // If so, we want to preserve those attributes.
486
+ this.listenTo(model, 'deleteContent', () => {
487
+ if (!isBackspace) {
488
+ return;
489
+ }
490
+ const position = selection.getFirstPosition();
491
+ shouldPreserveAttributes = hasAnyAttribute(selection, this.attributes) &&
492
+ !isStepAfterAnyAttributeBoundary(position, this.attributes);
493
+ }, { priority: 'high' });
494
+ // After removing the content, check whether the current selection should preserve the `linkHref` attribute.
495
+ this.listenTo(model, 'deleteContent', () => {
496
+ if (!isBackspace) {
497
+ return;
498
+ }
499
+ isBackspace = false;
500
+ // Do not escape two-step attribute if it was inside it before content deletion.
501
+ if (shouldPreserveAttributes) {
502
+ return;
503
+ }
504
+ // Use `model.enqueueChange()` in order to execute the callback at the end of the changes process.
505
+ editor.model.enqueueChange(() => {
506
+ const position = selection.getFirstPosition();
507
+ if (hasAnyAttribute(selection, this.attributes) &&
508
+ isBetweenDifferentAttributes(position, this.attributes)) {
509
+ if (position.isAtStart || isBetweenDifferentAttributes(position, this.attributes, true)) {
510
+ clearSelectionAttributes(model, this.attributes);
511
+ }
512
+ else if (!this._isGravityOverridden) {
513
+ this._overrideGravity();
514
+ }
515
+ }
516
+ });
517
+ }, { priority: 'low' });
518
+ }
343
519
  /**
344
520
  * `true` when the gravity is overridden for the plugin.
345
521
  */
@@ -404,6 +580,14 @@ function setSelectionAttributesFromTheNodeBefore(model, attributes, position) {
404
580
  }
405
581
  });
406
582
  }
583
+ /**
584
+ * Removes 2-SCM attributes from the selection.
585
+ */
586
+ function clearSelectionAttributes(model, attributes) {
587
+ model.change(writer => {
588
+ writer.removeSelectionAttribute(attributes);
589
+ });
590
+ }
407
591
  /**
408
592
  * Prevents the caret movement in the view by calling `preventDefault` on the event data.
409
593
  *
@@ -422,11 +606,14 @@ function isStepAfterAnyAttributeBoundary(position, attributes) {
422
606
  /**
423
607
  * Checks whether the given position is between different values of given attributes.
424
608
  */
425
- function isBetweenDifferentAttributes(position, attributes) {
609
+ function isBetweenDifferentAttributes(position, attributes, isStrict = false) {
426
610
  const { nodeBefore, nodeAfter } = position;
427
611
  for (const observedAttribute of attributes) {
428
612
  const attrBefore = nodeBefore ? nodeBefore.getAttribute(observedAttribute) : undefined;
429
613
  const attrAfter = nodeAfter ? nodeAfter.getAttribute(observedAttribute) : undefined;
614
+ if (isStrict && (attrBefore === undefined || attrAfter === undefined)) {
615
+ continue;
616
+ }
430
617
  if (attrAfter !== attrBefore) {
431
618
  return true;
432
619
  }
@@ -164,7 +164,7 @@ export interface TextTransformationConfig {
164
164
  * * If an array is passed, it has to have the same number of elements as there are capturing groups in the input value regular expression.
165
165
  * Each capture group will be replaced with a corresponding string from the passed array. If a given capturing group should not be replaced,
166
166
  * use `null` instead of passing a string.
167
- * * If a function is used, it should return an array as described above. The function is passed one parameter &mdash; an array with matches
167
+ * * If a function is used, it should return an array as described above. The function is passed one parameter &ndash; an array with matches
168
168
  * by the regular expression. See the examples below.
169
169
  *
170
170
  * A simple string-to-string replacement: