@ckeditor/ckeditor5-find-and-replace 41.4.2 → 42.0.0-alpha.1
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/README.md +6 -0
- package/dist/index.js +526 -423
- package/dist/index.js.map +1 -1
- package/dist/types/findandreplacestate.d.ts +1 -1
- package/package.json +3 -3
- package/src/findandreplacestate.d.ts +1 -1
- package/src/findandreplacestate.js +1 -1
package/dist/index.js
CHANGED
|
@@ -3,14 +3,156 @@
|
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
4
4
|
*/
|
|
5
5
|
import { icons, Plugin, Command } from '@ckeditor/ckeditor5-core/dist/index.js';
|
|
6
|
-
import { View, submitHandler, CollapsibleView, SwitchButtonView, ButtonView, LabeledFieldView, createLabeledInputText,
|
|
6
|
+
import { View, ViewCollection, FocusCycler, submitHandler, CollapsibleView, SwitchButtonView, ButtonView, LabeledFieldView, createLabeledInputText, Dialog, DropdownView, createDropdown, FormHeaderView, MenuBarMenuListItemButtonView, DialogViewPosition, CssTransitionDisablerMixin } from '@ckeditor/ckeditor5-ui/dist/index.js';
|
|
7
7
|
import { FocusTracker, KeystrokeHandler, isVisible, Rect, Collection, ObservableMixin, uid, scrollViewportToShowTarget } from '@ckeditor/ckeditor5-utils/dist/index.js';
|
|
8
8
|
import { escapeRegExp, debounce } from 'lodash-es';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The find and replace form view class.
|
|
12
|
+
*
|
|
13
|
+
* See {@link module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView}.
|
|
14
|
+
*/ class FindAndReplaceFormView extends View {
|
|
15
|
+
/**
|
|
16
|
+
* A collection of child views.
|
|
17
|
+
*/ children;
|
|
18
|
+
/**
|
|
19
|
+
* The find in text input view that stores the searched string.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/ _findInputView;
|
|
23
|
+
/**
|
|
24
|
+
* The replace input view.
|
|
25
|
+
*/ _replaceInputView;
|
|
26
|
+
/**
|
|
27
|
+
* The find button view that initializes the search process.
|
|
28
|
+
*/ _findButtonView;
|
|
29
|
+
/**
|
|
30
|
+
* The find previous button view.
|
|
31
|
+
*/ _findPrevButtonView;
|
|
32
|
+
/**
|
|
33
|
+
* The find next button view.
|
|
34
|
+
*/ _findNextButtonView;
|
|
35
|
+
/**
|
|
36
|
+
* A collapsible view aggregating the advanced search options.
|
|
37
|
+
*/ _advancedOptionsCollapsibleView;
|
|
38
|
+
/**
|
|
39
|
+
* A switch button view controlling the "Match case" option.
|
|
40
|
+
*/ _matchCaseSwitchView;
|
|
41
|
+
/**
|
|
42
|
+
* A switch button view controlling the "Whole words only" option.
|
|
43
|
+
*/ _wholeWordsOnlySwitchView;
|
|
44
|
+
/**
|
|
45
|
+
* The replace button view.
|
|
46
|
+
*/ _replaceButtonView;
|
|
47
|
+
/**
|
|
48
|
+
* The replace all button view.
|
|
49
|
+
*/ _replaceAllButtonView;
|
|
50
|
+
/**
|
|
51
|
+
* The `div` aggregating the inputs.
|
|
52
|
+
*/ _inputsDivView;
|
|
53
|
+
/**
|
|
54
|
+
* The `div` aggregating the action buttons.
|
|
55
|
+
*/ _actionButtonsDivView;
|
|
56
|
+
/**
|
|
57
|
+
* Tracks information about the DOM focus in the form.
|
|
58
|
+
*/ _focusTracker;
|
|
59
|
+
/**
|
|
60
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
61
|
+
*/ _keystrokes;
|
|
62
|
+
/**
|
|
63
|
+
* A collection of views that can be focused in the form.
|
|
64
|
+
*/ _focusables;
|
|
65
|
+
/**
|
|
66
|
+
* Helps cycling over {@link #_focusables} in the form.
|
|
67
|
+
*/ focusCycler;
|
|
68
|
+
/**
|
|
69
|
+
* Creates a view of find and replace form.
|
|
70
|
+
*
|
|
71
|
+
* @param locale The localization services instance.
|
|
72
|
+
*/ constructor(locale){
|
|
73
|
+
super(locale);
|
|
74
|
+
const t = locale.t;
|
|
75
|
+
this.children = this.createCollection();
|
|
76
|
+
this.set('matchCount', 0);
|
|
77
|
+
this.set('highlightOffset', 0);
|
|
78
|
+
this.set('isDirty', false);
|
|
79
|
+
this.set('_areCommandsEnabled', {});
|
|
80
|
+
this.set('_resultsCounterText', '');
|
|
81
|
+
this.set('_matchCase', false);
|
|
82
|
+
this.set('_wholeWordsOnly', false);
|
|
83
|
+
this.bind('_searchResultsFound').to(this, 'matchCount', this, 'isDirty', (matchCount, isDirty)=>{
|
|
84
|
+
return matchCount > 0 && !isDirty;
|
|
85
|
+
});
|
|
86
|
+
this._findInputView = this._createInputField(t('Find in text…'));
|
|
87
|
+
this._findPrevButtonView = this._createButton({
|
|
88
|
+
label: t('Previous result'),
|
|
89
|
+
class: 'ck-button-prev',
|
|
90
|
+
icon: icons.previousArrow,
|
|
91
|
+
keystroke: 'Shift+F3',
|
|
92
|
+
tooltip: true
|
|
93
|
+
});
|
|
94
|
+
this._findNextButtonView = this._createButton({
|
|
95
|
+
label: t('Next result'),
|
|
96
|
+
class: 'ck-button-next',
|
|
97
|
+
icon: icons.previousArrow,
|
|
98
|
+
keystroke: 'F3',
|
|
99
|
+
tooltip: true
|
|
100
|
+
});
|
|
101
|
+
this._replaceInputView = this._createInputField(t('Replace with…'), 'ck-labeled-field-replace');
|
|
102
|
+
this._inputsDivView = this._createInputsDiv();
|
|
103
|
+
this._matchCaseSwitchView = this._createMatchCaseSwitch();
|
|
104
|
+
this._wholeWordsOnlySwitchView = this._createWholeWordsOnlySwitch();
|
|
105
|
+
this._advancedOptionsCollapsibleView = this._createAdvancedOptionsCollapsible();
|
|
106
|
+
this._replaceAllButtonView = this._createButton({
|
|
107
|
+
label: t('Replace all'),
|
|
108
|
+
class: 'ck-button-replaceall',
|
|
109
|
+
withText: true
|
|
110
|
+
});
|
|
111
|
+
this._replaceButtonView = this._createButton({
|
|
112
|
+
label: t('Replace'),
|
|
113
|
+
class: 'ck-button-replace',
|
|
114
|
+
withText: true
|
|
115
|
+
});
|
|
116
|
+
this._findButtonView = this._createButton({
|
|
117
|
+
label: t('Find'),
|
|
118
|
+
class: 'ck-button-find ck-button-action',
|
|
119
|
+
withText: true
|
|
120
|
+
});
|
|
121
|
+
this._actionButtonsDivView = this._createActionButtonsDiv();
|
|
122
|
+
this._focusTracker = new FocusTracker();
|
|
123
|
+
this._keystrokes = new KeystrokeHandler();
|
|
124
|
+
this._focusables = new ViewCollection();
|
|
125
|
+
this.focusCycler = new FocusCycler({
|
|
126
|
+
focusables: this._focusables,
|
|
127
|
+
focusTracker: this._focusTracker,
|
|
128
|
+
keystrokeHandler: this._keystrokes,
|
|
129
|
+
actions: {
|
|
130
|
+
// Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
|
|
131
|
+
focusPrevious: 'shift + tab',
|
|
132
|
+
// Navigate form fields forwards using the <kbd>Tab</kbd> key.
|
|
133
|
+
focusNext: 'tab'
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
this.children.addMany([
|
|
137
|
+
this._inputsDivView,
|
|
138
|
+
this._advancedOptionsCollapsibleView,
|
|
139
|
+
this._actionButtonsDivView
|
|
140
|
+
]);
|
|
141
|
+
this.setTemplate({
|
|
142
|
+
tag: 'form',
|
|
143
|
+
attributes: {
|
|
144
|
+
class: [
|
|
145
|
+
'ck',
|
|
146
|
+
'ck-find-and-replace-form'
|
|
147
|
+
],
|
|
148
|
+
tabindex: '-1'
|
|
149
|
+
},
|
|
150
|
+
children: this.children
|
|
151
|
+
});
|
|
152
|
+
}
|
|
11
153
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
154
|
+
* @inheritDoc
|
|
155
|
+
*/ render() {
|
|
14
156
|
super.render();
|
|
15
157
|
submitHandler({
|
|
16
158
|
view: this
|
|
@@ -19,15 +161,15 @@ class FindAndReplaceFormView extends View {
|
|
|
19
161
|
this._initKeystrokeHandling();
|
|
20
162
|
}
|
|
21
163
|
/**
|
|
22
|
-
|
|
23
|
-
|
|
164
|
+
* @inheritDoc
|
|
165
|
+
*/ destroy() {
|
|
24
166
|
super.destroy();
|
|
25
167
|
this._focusTracker.destroy();
|
|
26
168
|
this._keystrokes.destroy();
|
|
27
169
|
}
|
|
28
170
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
171
|
+
* @inheritDoc
|
|
172
|
+
*/ focus(direction) {
|
|
31
173
|
if (direction === -1) {
|
|
32
174
|
this.focusCycler.focusLast();
|
|
33
175
|
} else {
|
|
@@ -35,31 +177,31 @@ class FindAndReplaceFormView extends View {
|
|
|
35
177
|
}
|
|
36
178
|
}
|
|
37
179
|
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
180
|
+
* Resets the form before re-appearing.
|
|
181
|
+
*
|
|
182
|
+
* It clears error messages, hides the match counter and disables the replace feature
|
|
183
|
+
* until the next hit of the "Find" button.
|
|
184
|
+
*
|
|
185
|
+
* **Note**: It does not reset inputs and options, though. This way the form works better in editors with
|
|
186
|
+
* disappearing toolbar (e.g. BalloonEditor): hiding the toolbar by accident (together with the find and replace UI)
|
|
187
|
+
* does not require filling the entire form again.
|
|
188
|
+
*/ reset() {
|
|
47
189
|
this._findInputView.errorText = null;
|
|
48
190
|
this.isDirty = true;
|
|
49
191
|
}
|
|
50
192
|
/**
|
|
51
|
-
|
|
52
|
-
|
|
193
|
+
* Returns the value of the find input.
|
|
194
|
+
*/ get _textToFind() {
|
|
53
195
|
return this._findInputView.fieldView.element.value;
|
|
54
196
|
}
|
|
55
197
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
198
|
+
* Returns the value of the replace input.
|
|
199
|
+
*/ get _textToReplace() {
|
|
58
200
|
return this._replaceInputView.fieldView.element.value;
|
|
59
201
|
}
|
|
60
202
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
203
|
+
* Configures and returns the `<div>` aggregating all form inputs.
|
|
204
|
+
*/ _createInputsDiv() {
|
|
63
205
|
const locale = this.locale;
|
|
64
206
|
const t = locale.t;
|
|
65
207
|
const inputsDivView = new View(locale);
|
|
@@ -99,8 +241,8 @@ class FindAndReplaceFormView extends View {
|
|
|
99
241
|
return inputsDivView;
|
|
100
242
|
}
|
|
101
243
|
/**
|
|
102
|
-
|
|
103
|
-
|
|
244
|
+
* The action performed when the {@link #_findButtonView} is pressed.
|
|
245
|
+
*/ _onFindButtonExecute() {
|
|
104
246
|
// When hitting "Find" in an empty input, an error should be displayed.
|
|
105
247
|
// Also, if the form was "dirty", it should remain so.
|
|
106
248
|
if (!this._textToFind) {
|
|
@@ -117,8 +259,8 @@ class FindAndReplaceFormView extends View {
|
|
|
117
259
|
});
|
|
118
260
|
}
|
|
119
261
|
/**
|
|
120
|
-
|
|
121
|
-
|
|
262
|
+
* Configures an injects the find results counter displaying a "N of M" label of the {@link #_findInputView}.
|
|
263
|
+
*/ _injectFindResultsCounter() {
|
|
122
264
|
const locale = this.locale;
|
|
123
265
|
const t = locale.t;
|
|
124
266
|
const bind = this.bindTemplate;
|
|
@@ -175,8 +317,8 @@ class FindAndReplaceFormView extends View {
|
|
|
175
317
|
this._findInputView.template.children[0].children.push(resultsCounterView);
|
|
176
318
|
}
|
|
177
319
|
/**
|
|
178
|
-
|
|
179
|
-
|
|
320
|
+
* Creates the collapsible view aggregating the advanced search options.
|
|
321
|
+
*/ _createAdvancedOptionsCollapsible() {
|
|
180
322
|
const t = this.locale.t;
|
|
181
323
|
const collapsible = new CollapsibleView(this.locale, [
|
|
182
324
|
this._matchCaseSwitchView,
|
|
@@ -189,8 +331,8 @@ class FindAndReplaceFormView extends View {
|
|
|
189
331
|
return collapsible;
|
|
190
332
|
}
|
|
191
333
|
/**
|
|
192
|
-
|
|
193
|
-
|
|
334
|
+
* Configures and returns the `<div>` element aggregating all form action buttons.
|
|
335
|
+
*/ _createActionButtonsDiv() {
|
|
194
336
|
const actionsDivView = new View(this.locale);
|
|
195
337
|
this._replaceButtonView.bind('isEnabled').to(this, '_areCommandsEnabled', this, '_searchResultsFound', ({ replace }, resultsFound)=>replace && resultsFound);
|
|
196
338
|
this._replaceAllButtonView.bind('isEnabled').to(this, '_areCommandsEnabled', this, '_searchResultsFound', ({ replaceAll }, resultsFound)=>replaceAll && resultsFound);
|
|
@@ -225,9 +367,9 @@ class FindAndReplaceFormView extends View {
|
|
|
225
367
|
return actionsDivView;
|
|
226
368
|
}
|
|
227
369
|
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
370
|
+
* Creates, configures and returns and instance of a dropdown allowing users to narrow
|
|
371
|
+
* the search criteria down. The dropdown has a list with switch buttons for each option.
|
|
372
|
+
*/ _createMatchCaseSwitch() {
|
|
231
373
|
const t = this.locale.t;
|
|
232
374
|
const matchCaseSwitchButton = new SwitchButtonView(this.locale);
|
|
233
375
|
matchCaseSwitchButton.set({
|
|
@@ -246,9 +388,9 @@ class FindAndReplaceFormView extends View {
|
|
|
246
388
|
return matchCaseSwitchButton;
|
|
247
389
|
}
|
|
248
390
|
/**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
391
|
+
* Creates, configures and returns and instance of a dropdown allowing users to narrow
|
|
392
|
+
* the search criteria down. The dropdown has a list with switch buttons for each option.
|
|
393
|
+
*/ _createWholeWordsOnlySwitch() {
|
|
252
394
|
const t = this.locale.t;
|
|
253
395
|
const wholeWordsOnlySwitchButton = new SwitchButtonView(this.locale);
|
|
254
396
|
wholeWordsOnlySwitchButton.set({
|
|
@@ -267,9 +409,9 @@ class FindAndReplaceFormView extends View {
|
|
|
267
409
|
return wholeWordsOnlySwitchButton;
|
|
268
410
|
}
|
|
269
411
|
/**
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
412
|
+
* Initializes the {@link #_focusables} and {@link #_focusTracker} to allow navigation
|
|
413
|
+
* using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keystrokes in the right order.
|
|
414
|
+
*/ _initFocusCycling() {
|
|
273
415
|
const childViews = [
|
|
274
416
|
this._findInputView,
|
|
275
417
|
this._findPrevButtonView,
|
|
@@ -290,8 +432,8 @@ class FindAndReplaceFormView extends View {
|
|
|
290
432
|
});
|
|
291
433
|
}
|
|
292
434
|
/**
|
|
293
|
-
|
|
294
|
-
|
|
435
|
+
* Initializes the keystroke handling in the form.
|
|
436
|
+
*/ _initKeystrokeHandling() {
|
|
295
437
|
const stopPropagation = (data)=>data.stopPropagation();
|
|
296
438
|
const stopPropagationAndPreventDefault = (data)=>{
|
|
297
439
|
data.stopPropagation();
|
|
@@ -346,131 +488,61 @@ class FindAndReplaceFormView extends View {
|
|
|
346
488
|
this._keystrokes.set('arrowdown', stopPropagation);
|
|
347
489
|
}
|
|
348
490
|
/**
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
491
|
+
* Creates a button view.
|
|
492
|
+
*
|
|
493
|
+
* @param options The properties of the `ButtonView`.
|
|
494
|
+
* @returns The button view instance.
|
|
495
|
+
*/ _createButton(options) {
|
|
354
496
|
const button = new ButtonView(this.locale);
|
|
355
497
|
button.set(options);
|
|
356
498
|
return button;
|
|
357
499
|
}
|
|
358
500
|
/**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
501
|
+
* Creates a labeled input view.
|
|
502
|
+
*
|
|
503
|
+
* @param label The input label.
|
|
504
|
+
* @returns The labeled input view instance.
|
|
505
|
+
*/ _createInputField(label, className) {
|
|
364
506
|
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
|
365
507
|
labeledInput.label = label;
|
|
366
508
|
labeledInput.class = className;
|
|
367
509
|
return labeledInput;
|
|
368
510
|
}
|
|
369
|
-
/**
|
|
370
|
-
* Creates a view of find and replace form.
|
|
371
|
-
*
|
|
372
|
-
* @param locale The localization services instance.
|
|
373
|
-
*/ constructor(locale){
|
|
374
|
-
super(locale);
|
|
375
|
-
const t = locale.t;
|
|
376
|
-
this.children = this.createCollection();
|
|
377
|
-
this.set('matchCount', 0);
|
|
378
|
-
this.set('highlightOffset', 0);
|
|
379
|
-
this.set('isDirty', false);
|
|
380
|
-
this.set('_areCommandsEnabled', {});
|
|
381
|
-
this.set('_resultsCounterText', '');
|
|
382
|
-
this.set('_matchCase', false);
|
|
383
|
-
this.set('_wholeWordsOnly', false);
|
|
384
|
-
this.bind('_searchResultsFound').to(this, 'matchCount', this, 'isDirty', (matchCount, isDirty)=>{
|
|
385
|
-
return matchCount > 0 && !isDirty;
|
|
386
|
-
});
|
|
387
|
-
this._findInputView = this._createInputField(t('Find in text…'));
|
|
388
|
-
this._findPrevButtonView = this._createButton({
|
|
389
|
-
label: t('Previous result'),
|
|
390
|
-
class: 'ck-button-prev',
|
|
391
|
-
icon: icons.previousArrow,
|
|
392
|
-
keystroke: 'Shift+F3',
|
|
393
|
-
tooltip: true
|
|
394
|
-
});
|
|
395
|
-
this._findNextButtonView = this._createButton({
|
|
396
|
-
label: t('Next result'),
|
|
397
|
-
class: 'ck-button-next',
|
|
398
|
-
icon: icons.previousArrow,
|
|
399
|
-
keystroke: 'F3',
|
|
400
|
-
tooltip: true
|
|
401
|
-
});
|
|
402
|
-
this._replaceInputView = this._createInputField(t('Replace with…'), 'ck-labeled-field-replace');
|
|
403
|
-
this._inputsDivView = this._createInputsDiv();
|
|
404
|
-
this._matchCaseSwitchView = this._createMatchCaseSwitch();
|
|
405
|
-
this._wholeWordsOnlySwitchView = this._createWholeWordsOnlySwitch();
|
|
406
|
-
this._advancedOptionsCollapsibleView = this._createAdvancedOptionsCollapsible();
|
|
407
|
-
this._replaceAllButtonView = this._createButton({
|
|
408
|
-
label: t('Replace all'),
|
|
409
|
-
class: 'ck-button-replaceall',
|
|
410
|
-
withText: true
|
|
411
|
-
});
|
|
412
|
-
this._replaceButtonView = this._createButton({
|
|
413
|
-
label: t('Replace'),
|
|
414
|
-
class: 'ck-button-replace',
|
|
415
|
-
withText: true
|
|
416
|
-
});
|
|
417
|
-
this._findButtonView = this._createButton({
|
|
418
|
-
label: t('Find'),
|
|
419
|
-
class: 'ck-button-find ck-button-action',
|
|
420
|
-
withText: true
|
|
421
|
-
});
|
|
422
|
-
this._actionButtonsDivView = this._createActionButtonsDiv();
|
|
423
|
-
this._focusTracker = new FocusTracker();
|
|
424
|
-
this._keystrokes = new KeystrokeHandler();
|
|
425
|
-
this._focusables = new ViewCollection();
|
|
426
|
-
this.focusCycler = new FocusCycler({
|
|
427
|
-
focusables: this._focusables,
|
|
428
|
-
focusTracker: this._focusTracker,
|
|
429
|
-
keystrokeHandler: this._keystrokes,
|
|
430
|
-
actions: {
|
|
431
|
-
// Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
|
|
432
|
-
focusPrevious: 'shift + tab',
|
|
433
|
-
// Navigate form fields forwards using the <kbd>Tab</kbd> key.
|
|
434
|
-
focusNext: 'tab'
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
this.children.addMany([
|
|
438
|
-
this._inputsDivView,
|
|
439
|
-
this._advancedOptionsCollapsibleView,
|
|
440
|
-
this._actionButtonsDivView
|
|
441
|
-
]);
|
|
442
|
-
this.setTemplate({
|
|
443
|
-
tag: 'form',
|
|
444
|
-
attributes: {
|
|
445
|
-
class: [
|
|
446
|
-
'ck',
|
|
447
|
-
'ck-find-and-replace-form'
|
|
448
|
-
],
|
|
449
|
-
tabindex: '-1'
|
|
450
|
-
},
|
|
451
|
-
children: this.children
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
511
|
}
|
|
455
512
|
|
|
456
513
|
var loupeIcon = "<svg viewBox=\"0 0 20 20\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m12.87 13.786 1.532-1.286 3.857 4.596a1 1 0 1 1-1.532 1.286l-3.857-4.596z\"/><path d=\"M16.004 8.5a6.5 6.5 0 0 1-9.216 5.905c-1.154-.53-.863-1.415-.663-1.615.194-.194.564-.592 1.635-.141a4.5 4.5 0 0 0 5.89-5.904l-.104-.227 1.332-1.331c.045-.046.196-.041.224.007a6.47 6.47 0 0 1 .902 3.306zm-3.4-5.715c.562.305.742 1.106.354 1.494-.388.388-.995.414-1.476.178a4.5 4.5 0 0 0-6.086 5.882l.114.236-1.348 1.349c-.038.037-.17.022-.198-.023a6.5 6.5 0 0 1 5.54-9.9 6.469 6.469 0 0 1 3.1.784z\"/><path d=\"M4.001 11.93.948 8.877a.2.2 0 0 1 .141-.341h6.106a.2.2 0 0 1 .141.341L4.283 11.93a.2.2 0 0 1-.282 0zm11.083-6.789 3.053 3.053a.2.2 0 0 1-.14.342H11.89a.2.2 0 0 1-.14-.342l3.052-3.053a.2.2 0 0 1 .282 0z\"/></svg>";
|
|
457
514
|
|
|
458
|
-
|
|
515
|
+
/**
|
|
516
|
+
* The default find and replace UI.
|
|
517
|
+
*
|
|
518
|
+
* It registers the `'findAndReplace'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}.
|
|
519
|
+
* that uses the {@link module:find-and-replace/findandreplace~FindAndReplace FindAndReplace} plugin API.
|
|
520
|
+
*/ class FindAndReplaceUI extends Plugin {
|
|
459
521
|
/**
|
|
460
|
-
|
|
461
|
-
|
|
522
|
+
* @inheritDoc
|
|
523
|
+
*/ static get requires() {
|
|
462
524
|
return [
|
|
463
525
|
Dialog
|
|
464
526
|
];
|
|
465
527
|
}
|
|
466
528
|
/**
|
|
467
|
-
|
|
468
|
-
|
|
529
|
+
* @inheritDoc
|
|
530
|
+
*/ static get pluginName() {
|
|
469
531
|
return 'FindAndReplaceUI';
|
|
470
532
|
}
|
|
471
533
|
/**
|
|
472
|
-
|
|
473
|
-
|
|
534
|
+
* A reference to the find and replace form view.
|
|
535
|
+
*/ formView;
|
|
536
|
+
/**
|
|
537
|
+
* @inheritDoc
|
|
538
|
+
*/ constructor(editor){
|
|
539
|
+
super(editor);
|
|
540
|
+
editor.config.define('findAndReplace.uiType', 'dialog');
|
|
541
|
+
this.formView = null;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* @inheritDoc
|
|
545
|
+
*/ init() {
|
|
474
546
|
const editor = this.editor;
|
|
475
547
|
const isUiUsingDropdown = editor.config.get('findAndReplace.uiType') === 'dropdown';
|
|
476
548
|
const findCommand = editor.commands.get('find');
|
|
@@ -525,8 +597,8 @@ class FindAndReplaceUI extends Plugin {
|
|
|
525
597
|
});
|
|
526
598
|
}
|
|
527
599
|
/**
|
|
528
|
-
|
|
529
|
-
|
|
600
|
+
* Creates a dropdown containing the find and replace form.
|
|
601
|
+
*/ _createDropdown() {
|
|
530
602
|
const editor = this.editor;
|
|
531
603
|
const t = editor.locale.t;
|
|
532
604
|
const dropdownView = createDropdown(editor.locale);
|
|
@@ -563,8 +635,8 @@ class FindAndReplaceUI extends Plugin {
|
|
|
563
635
|
return dropdownView;
|
|
564
636
|
}
|
|
565
637
|
/**
|
|
566
|
-
|
|
567
|
-
|
|
638
|
+
* Creates a button that opens a dialog with the find and replace form.
|
|
639
|
+
*/ _createDialogButtonForToolbar() {
|
|
568
640
|
const editor = this.editor;
|
|
569
641
|
const buttonView = this._createButton(ButtonView);
|
|
570
642
|
const dialog = editor.plugins.get('Dialog');
|
|
@@ -587,8 +659,8 @@ class FindAndReplaceUI extends Plugin {
|
|
|
587
659
|
return buttonView;
|
|
588
660
|
}
|
|
589
661
|
/**
|
|
590
|
-
|
|
591
|
-
|
|
662
|
+
* Creates a button for for menu bar that will show find and replace dialog.
|
|
663
|
+
*/ _createDialogButtonForMenuBar() {
|
|
592
664
|
const buttonView = this._createButton(MenuBarMenuListItemButtonView);
|
|
593
665
|
const dialogPlugin = this.editor.plugins.get('Dialog');
|
|
594
666
|
buttonView.on('execute', ()=>{
|
|
@@ -601,8 +673,8 @@ class FindAndReplaceUI extends Plugin {
|
|
|
601
673
|
return buttonView;
|
|
602
674
|
}
|
|
603
675
|
/**
|
|
604
|
-
|
|
605
|
-
|
|
676
|
+
* Creates a button for find and replace command to use either in toolbar or in menu bar.
|
|
677
|
+
*/ _createButton(ButtonClass) {
|
|
606
678
|
const editor = this.editor;
|
|
607
679
|
const findCommand = editor.commands.get('find');
|
|
608
680
|
const buttonView = new ButtonClass(editor.locale);
|
|
@@ -617,8 +689,8 @@ class FindAndReplaceUI extends Plugin {
|
|
|
617
689
|
return buttonView;
|
|
618
690
|
}
|
|
619
691
|
/**
|
|
620
|
-
|
|
621
|
-
|
|
692
|
+
* Shows the find and replace dialog.
|
|
693
|
+
*/ _showDialog() {
|
|
622
694
|
const editor = this.editor;
|
|
623
695
|
const dialog = editor.plugins.get('Dialog');
|
|
624
696
|
const t = editor.locale.t;
|
|
@@ -639,10 +711,10 @@ class FindAndReplaceUI extends Plugin {
|
|
|
639
711
|
});
|
|
640
712
|
}
|
|
641
713
|
/**
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
714
|
+
* Sets up the form view for the find and replace.
|
|
715
|
+
*
|
|
716
|
+
* @param formView A related form view.
|
|
717
|
+
*/ _createFormView() {
|
|
646
718
|
const editor = this.editor;
|
|
647
719
|
const formView = new (CssTransitionDisablerMixin(FindAndReplaceFormView))(editor.locale);
|
|
648
720
|
const commands = editor.commands;
|
|
@@ -678,33 +750,44 @@ class FindAndReplaceUI extends Plugin {
|
|
|
678
750
|
return formView;
|
|
679
751
|
}
|
|
680
752
|
/**
|
|
681
|
-
|
|
682
|
-
|
|
753
|
+
* Clears the find and replace form and focuses the search text field.
|
|
754
|
+
*/ _setupFormView() {
|
|
683
755
|
this.formView.disableCssTransitions();
|
|
684
756
|
this.formView.reset();
|
|
685
757
|
this.formView._findInputView.fieldView.select();
|
|
686
758
|
this.formView.enableCssTransitions();
|
|
687
759
|
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* The find command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
764
|
+
*/ class FindCommand extends Command {
|
|
688
765
|
/**
|
|
689
|
-
|
|
690
|
-
|
|
766
|
+
* The find and replace state object used for command operations.
|
|
767
|
+
*/ _state;
|
|
768
|
+
/**
|
|
769
|
+
* Creates a new `FindCommand` instance.
|
|
770
|
+
*
|
|
771
|
+
* @param editor The editor on which this command will be used.
|
|
772
|
+
* @param state An object to hold plugin state.
|
|
773
|
+
*/ constructor(editor, state){
|
|
691
774
|
super(editor);
|
|
692
|
-
|
|
693
|
-
this.
|
|
775
|
+
// The find command is always enabled.
|
|
776
|
+
this.isEnabled = true;
|
|
777
|
+
// It does not affect data so should be enabled in read-only mode.
|
|
778
|
+
this.affectsData = false;
|
|
779
|
+
this._state = state;
|
|
694
780
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
*
|
|
706
|
-
* @fires execute
|
|
707
|
-
*/ execute(callbackOrText, { matchCase, wholeWords } = {}) {
|
|
781
|
+
/**
|
|
782
|
+
* Executes the command.
|
|
783
|
+
*
|
|
784
|
+
* @param callbackOrText
|
|
785
|
+
* @param options Options object.
|
|
786
|
+
* @param options.matchCase If set to `true`, the letter case will be matched.
|
|
787
|
+
* @param options.wholeWords If set to `true`, only whole words that match `callbackOrText` will be matched.
|
|
788
|
+
*
|
|
789
|
+
* @fires execute
|
|
790
|
+
*/ execute(callbackOrText, { matchCase, wholeWords } = {}) {
|
|
708
791
|
const { editor } = this;
|
|
709
792
|
const { model } = editor;
|
|
710
793
|
const findAndReplaceUtils = editor.plugins.get('FindAndReplaceUtils');
|
|
@@ -737,28 +820,31 @@ class FindCommand extends Command {
|
|
|
737
820
|
findCallback
|
|
738
821
|
};
|
|
739
822
|
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
class ReplaceCommandBase extends Command {
|
|
740
826
|
/**
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
827
|
+
* The find and replace state object used for command operations.
|
|
828
|
+
*/ _state;
|
|
829
|
+
/**
|
|
830
|
+
* Creates a new `ReplaceCommand` instance.
|
|
831
|
+
*
|
|
832
|
+
* @param editor Editor on which this command will be used.
|
|
833
|
+
* @param state An object to hold plugin state.
|
|
834
|
+
*/ constructor(editor, state){
|
|
746
835
|
super(editor);
|
|
747
|
-
// The
|
|
836
|
+
// The replace command is always enabled.
|
|
748
837
|
this.isEnabled = true;
|
|
749
|
-
// It does not affect data so should be enabled in read-only mode.
|
|
750
|
-
this.affectsData = false;
|
|
751
838
|
this._state = state;
|
|
839
|
+
// Since this command executes on particular result independent of selection, it should be checked directly in execute block.
|
|
840
|
+
this._isEnabledBasedOnSelection = false;
|
|
752
841
|
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
class ReplaceCommandBase extends Command {
|
|
756
842
|
/**
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
843
|
+
* Common logic for both `replace` commands.
|
|
844
|
+
* Replace a given find result by a string or a callback.
|
|
845
|
+
*
|
|
846
|
+
* @param result A single result from the find command.
|
|
847
|
+
*/ _replace(replacementText, result) {
|
|
762
848
|
const { model } = this.editor;
|
|
763
849
|
const range = result.marker.getRange();
|
|
764
850
|
// Don't replace a result that is in non-editable place.
|
|
@@ -784,53 +870,44 @@ class ReplaceCommandBase extends Command {
|
|
|
784
870
|
}
|
|
785
871
|
});
|
|
786
872
|
}
|
|
787
|
-
/**
|
|
788
|
-
* Creates a new `ReplaceCommand` instance.
|
|
789
|
-
*
|
|
790
|
-
* @param editor Editor on which this command will be used.
|
|
791
|
-
* @param state An object to hold plugin state.
|
|
792
|
-
*/ constructor(editor, state){
|
|
793
|
-
super(editor);
|
|
794
|
-
// The replace command is always enabled.
|
|
795
|
-
this.isEnabled = true;
|
|
796
|
-
this._state = state;
|
|
797
|
-
// Since this command executes on particular result independent of selection, it should be checked directly in execute block.
|
|
798
|
-
this._isEnabledBasedOnSelection = false;
|
|
799
|
-
}
|
|
800
873
|
}
|
|
801
874
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
875
|
+
/**
|
|
876
|
+
* The replace command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
877
|
+
*/ class ReplaceCommand extends ReplaceCommandBase {
|
|
878
|
+
/**
|
|
879
|
+
* Replace a given find result by a string or a callback.
|
|
880
|
+
*
|
|
881
|
+
* @param result A single result from the find command.
|
|
882
|
+
*
|
|
883
|
+
* @fires execute
|
|
884
|
+
*/ execute(replacementText, result) {
|
|
810
885
|
this._replace(replacementText, result);
|
|
811
886
|
}
|
|
812
887
|
}
|
|
813
888
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
889
|
+
/**
|
|
890
|
+
* The replace all command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
891
|
+
*/ class ReplaceAllCommand extends ReplaceCommandBase {
|
|
892
|
+
/**
|
|
893
|
+
* Replaces all the occurrences of `textToReplace` with a given `newText` string.
|
|
894
|
+
*
|
|
895
|
+
* ```ts
|
|
896
|
+
* replaceAllCommand.execute( 'replaceAll', 'new text replacement', 'text to replace' );
|
|
897
|
+
* ```
|
|
898
|
+
*
|
|
899
|
+
* Alternatively you can call it from editor instance:
|
|
900
|
+
*
|
|
901
|
+
* ```ts
|
|
902
|
+
* editor.execute( 'replaceAll', 'new text', 'old text' );
|
|
903
|
+
* ```
|
|
904
|
+
*
|
|
905
|
+
* @param newText Text that will be inserted to the editor for each match.
|
|
906
|
+
* @param textToReplace Text to be replaced or a collection of matches
|
|
907
|
+
* as returned by the find command.
|
|
908
|
+
*
|
|
909
|
+
* @fires module:core/command~Command#event:execute
|
|
910
|
+
*/ execute(newText, textToReplace) {
|
|
834
911
|
const { editor } = this;
|
|
835
912
|
const { model } = editor;
|
|
836
913
|
const findAndReplaceUtils = editor.plugins.get('FindAndReplaceUtils');
|
|
@@ -849,26 +926,20 @@ class ReplaceAllCommand extends ReplaceCommandBase {
|
|
|
849
926
|
}
|
|
850
927
|
}
|
|
851
928
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
929
|
+
/**
|
|
930
|
+
* The find next command. Moves the highlight to the next search result.
|
|
931
|
+
*
|
|
932
|
+
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
933
|
+
*/ class FindNextCommand extends Command {
|
|
858
934
|
/**
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const results = this._state.results;
|
|
862
|
-
const currentIndex = results.getIndex(this._state.highlightedResult);
|
|
863
|
-
const nextIndex = currentIndex + 1 >= results.length ? 0 : currentIndex + 1;
|
|
864
|
-
this._state.highlightedResult = this._state.results.get(nextIndex);
|
|
865
|
-
}
|
|
935
|
+
* The find and replace state object used for command operations.
|
|
936
|
+
*/ _state;
|
|
866
937
|
/**
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
938
|
+
* Creates a new `FindNextCommand` instance.
|
|
939
|
+
*
|
|
940
|
+
* @param editor The editor on which this command will be used.
|
|
941
|
+
* @param state An object to hold plugin state.
|
|
942
|
+
*/ constructor(editor, state){
|
|
872
943
|
super(editor);
|
|
873
944
|
// It does not affect data so should be enabled in read-only mode.
|
|
874
945
|
this.affectsData = false;
|
|
@@ -878,12 +949,29 @@ class FindNextCommand extends Command {
|
|
|
878
949
|
this.isEnabled = this._state.results.length > 1;
|
|
879
950
|
});
|
|
880
951
|
}
|
|
952
|
+
/**
|
|
953
|
+
* @inheritDoc
|
|
954
|
+
*/ refresh() {
|
|
955
|
+
this.isEnabled = this._state.results.length > 1;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* @inheritDoc
|
|
959
|
+
*/ execute() {
|
|
960
|
+
const results = this._state.results;
|
|
961
|
+
const currentIndex = results.getIndex(this._state.highlightedResult);
|
|
962
|
+
const nextIndex = currentIndex + 1 >= results.length ? 0 : currentIndex + 1;
|
|
963
|
+
this._state.highlightedResult = this._state.results.get(nextIndex);
|
|
964
|
+
}
|
|
881
965
|
}
|
|
882
966
|
|
|
883
|
-
|
|
967
|
+
/**
|
|
968
|
+
* The find previous command. Moves the highlight to the previous search result.
|
|
969
|
+
*
|
|
970
|
+
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
971
|
+
*/ class FindPreviousCommand extends FindNextCommand {
|
|
884
972
|
/**
|
|
885
|
-
|
|
886
|
-
|
|
973
|
+
* @inheritDoc
|
|
974
|
+
*/ execute() {
|
|
887
975
|
const results = this._state.results;
|
|
888
976
|
const currentIndex = results.getIndex(this._state.highlightedResult);
|
|
889
977
|
const previousIndex = currentIndex - 1 < 0 ? this._state.results.length - 1 : currentIndex - 1;
|
|
@@ -891,46 +979,12 @@ class FindPreviousCommand extends FindNextCommand {
|
|
|
891
979
|
}
|
|
892
980
|
}
|
|
893
981
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
*/ clear(model) {
|
|
898
|
-
this.searchText = '';
|
|
899
|
-
model.change((writer)=>{
|
|
900
|
-
if (this.highlightedResult) {
|
|
901
|
-
const oldMatchId = this.highlightedResult.marker.name.split(':')[1];
|
|
902
|
-
const oldMarker = model.markers.get(`findResultHighlighted:${oldMatchId}`);
|
|
903
|
-
if (oldMarker) {
|
|
904
|
-
writer.removeMarker(oldMarker);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
[
|
|
908
|
-
...this.results
|
|
909
|
-
].forEach(({ marker })=>{
|
|
910
|
-
writer.removeMarker(marker);
|
|
911
|
-
});
|
|
912
|
-
});
|
|
913
|
-
this.results.clear();
|
|
914
|
-
}
|
|
915
|
-
/**
|
|
916
|
-
* Refreshes the highlight result offset based on it's index within the result list.
|
|
917
|
-
*/ refreshHighlightOffset() {
|
|
918
|
-
const { highlightedResult, results } = this;
|
|
919
|
-
const sortMapping = {
|
|
920
|
-
before: -1,
|
|
921
|
-
same: 0,
|
|
922
|
-
after: 1,
|
|
923
|
-
different: 1
|
|
924
|
-
};
|
|
925
|
-
if (highlightedResult) {
|
|
926
|
-
this.highlightedOffset = Array.from(results).sort((a, b)=>sortMapping[a.marker.getStart().compareWith(b.marker.getStart())]).indexOf(highlightedResult) + 1;
|
|
927
|
-
} else {
|
|
928
|
-
this.highlightedOffset = 0;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
982
|
+
/**
|
|
983
|
+
* The object storing find and replace plugin state for a given editor instance.
|
|
984
|
+
*/ class FindAndReplaceState extends /* #__PURE__ */ ObservableMixin() {
|
|
931
985
|
/**
|
|
932
|
-
|
|
933
|
-
|
|
986
|
+
* Creates an instance of the state.
|
|
987
|
+
*/ constructor(model){
|
|
934
988
|
super();
|
|
935
989
|
this.set('results', new Collection());
|
|
936
990
|
this.set('highlightedResult', null);
|
|
@@ -963,34 +1017,72 @@ class FindAndReplaceState extends ObservableMixin() {
|
|
|
963
1017
|
this.refreshHighlightOffset();
|
|
964
1018
|
});
|
|
965
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Cleans the state up and removes markers from the model.
|
|
1022
|
+
*/ clear(model) {
|
|
1023
|
+
this.searchText = '';
|
|
1024
|
+
model.change((writer)=>{
|
|
1025
|
+
if (this.highlightedResult) {
|
|
1026
|
+
const oldMatchId = this.highlightedResult.marker.name.split(':')[1];
|
|
1027
|
+
const oldMarker = model.markers.get(`findResultHighlighted:${oldMatchId}`);
|
|
1028
|
+
if (oldMarker) {
|
|
1029
|
+
writer.removeMarker(oldMarker);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
[
|
|
1033
|
+
...this.results
|
|
1034
|
+
].forEach(({ marker })=>{
|
|
1035
|
+
writer.removeMarker(marker);
|
|
1036
|
+
});
|
|
1037
|
+
});
|
|
1038
|
+
this.results.clear();
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Refreshes the highlight result offset based on it's index within the result list.
|
|
1042
|
+
*/ refreshHighlightOffset() {
|
|
1043
|
+
const { highlightedResult, results } = this;
|
|
1044
|
+
const sortMapping = {
|
|
1045
|
+
before: -1,
|
|
1046
|
+
same: 0,
|
|
1047
|
+
after: 1,
|
|
1048
|
+
different: 1
|
|
1049
|
+
};
|
|
1050
|
+
if (highlightedResult) {
|
|
1051
|
+
this.highlightedOffset = Array.from(results).sort((a, b)=>sortMapping[a.marker.getStart().compareWith(b.marker.getStart())]).indexOf(highlightedResult) + 1;
|
|
1052
|
+
} else {
|
|
1053
|
+
this.highlightedOffset = 0;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
966
1056
|
}
|
|
967
1057
|
|
|
968
|
-
|
|
1058
|
+
/**
|
|
1059
|
+
* A set of helpers related to find and replace.
|
|
1060
|
+
*/ class FindAndReplaceUtils extends Plugin {
|
|
969
1061
|
/**
|
|
970
|
-
|
|
971
|
-
|
|
1062
|
+
* @inheritDoc
|
|
1063
|
+
*/ static get pluginName() {
|
|
972
1064
|
return 'FindAndReplaceUtils';
|
|
973
1065
|
}
|
|
974
1066
|
/**
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1067
|
+
* Executes findCallback and updates search results list.
|
|
1068
|
+
*
|
|
1069
|
+
* @param range The model range to scan for matches.
|
|
1070
|
+
* @param model The model.
|
|
1071
|
+
* @param findCallback The callback that should return `true` if provided text matches the search term.
|
|
1072
|
+
* @param startResults An optional collection of find matches that the function should
|
|
1073
|
+
* start with. This would be a collection returned by a previous `updateFindResultFromRange()` call.
|
|
1074
|
+
* @returns A collection of objects describing find match.
|
|
1075
|
+
*
|
|
1076
|
+
* An example structure:
|
|
1077
|
+
*
|
|
1078
|
+
* ```js
|
|
1079
|
+
* {
|
|
1080
|
+
* id: resultId,
|
|
1081
|
+
* label: foundItem.label,
|
|
1082
|
+
* marker
|
|
1083
|
+
* }
|
|
1084
|
+
* ```
|
|
1085
|
+
*/ updateFindResultFromRange(range, model, findCallback, startResults) {
|
|
994
1086
|
const results = startResults || new Collection();
|
|
995
1087
|
const checkIfResultAlreadyOnList = (marker)=>results.find((markerItem)=>{
|
|
996
1088
|
const { marker: resultsMarker } = markerItem;
|
|
@@ -1034,12 +1126,12 @@ class FindAndReplaceUtils extends Plugin {
|
|
|
1034
1126
|
return results;
|
|
1035
1127
|
}
|
|
1036
1128
|
/**
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1129
|
+
* Returns text representation of a range. The returned text length should be the same as range length.
|
|
1130
|
+
* In order to achieve this, this function will replace inline elements (text-line) as new line character ("\n").
|
|
1131
|
+
*
|
|
1132
|
+
* @param range The model range.
|
|
1133
|
+
* @returns The text content of the provided range.
|
|
1134
|
+
*/ rangeToText(range) {
|
|
1043
1135
|
return Array.from(range.getItems()).reduce((rangeText, node)=>{
|
|
1044
1136
|
// Trim text to a last occurrence of an inline element and update range start.
|
|
1045
1137
|
if (!(node.is('$text') || node.is('$textProxy'))) {
|
|
@@ -1051,13 +1143,13 @@ class FindAndReplaceUtils extends Plugin {
|
|
|
1051
1143
|
}, '');
|
|
1052
1144
|
}
|
|
1053
1145
|
/**
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1146
|
+
* Creates a text matching callback for a specified search term and matching options.
|
|
1147
|
+
*
|
|
1148
|
+
* @param searchTerm The search term.
|
|
1149
|
+
* @param options Matching options.
|
|
1150
|
+
* - options.matchCase=false If set to `true` letter casing will be ignored.
|
|
1151
|
+
* - options.wholeWords=false If set to `true` only whole words that match `callbackOrText` will be matched.
|
|
1152
|
+
*/ findByTextCallback(searchTerm, options) {
|
|
1061
1153
|
let flags = 'gu';
|
|
1062
1154
|
if (!options.matchCase) {
|
|
1063
1155
|
flags += 'i';
|
|
@@ -1107,22 +1199,27 @@ function findInsertIndex(resultsList, markerToInsert) {
|
|
|
1107
1199
|
}
|
|
1108
1200
|
|
|
1109
1201
|
const HIGHLIGHT_CLASS = 'ck-find-result_selected';
|
|
1110
|
-
|
|
1202
|
+
/**
|
|
1203
|
+
* Implements the editing part for find and replace plugin. For example conversion, commands etc.
|
|
1204
|
+
*/ class FindAndReplaceEditing extends Plugin {
|
|
1111
1205
|
/**
|
|
1112
|
-
|
|
1113
|
-
|
|
1206
|
+
* @inheritDoc
|
|
1207
|
+
*/ static get requires() {
|
|
1114
1208
|
return [
|
|
1115
1209
|
FindAndReplaceUtils
|
|
1116
1210
|
];
|
|
1117
1211
|
}
|
|
1118
1212
|
/**
|
|
1119
|
-
|
|
1120
|
-
|
|
1213
|
+
* @inheritDoc
|
|
1214
|
+
*/ static get pluginName() {
|
|
1121
1215
|
return 'FindAndReplaceEditing';
|
|
1122
1216
|
}
|
|
1123
1217
|
/**
|
|
1124
|
-
|
|
1125
|
-
|
|
1218
|
+
* An object storing the find and replace state within a given editor instance.
|
|
1219
|
+
*/ state;
|
|
1220
|
+
/**
|
|
1221
|
+
* @inheritDoc
|
|
1222
|
+
*/ init() {
|
|
1126
1223
|
this.state = new FindAndReplaceState(this.editor.model);
|
|
1127
1224
|
this.set('_isSearchActive', false);
|
|
1128
1225
|
this._defineConverters();
|
|
@@ -1174,21 +1271,21 @@ class FindAndReplaceEditing extends Plugin {
|
|
|
1174
1271
|
});
|
|
1175
1272
|
}
|
|
1176
1273
|
/**
|
|
1177
|
-
|
|
1178
|
-
|
|
1274
|
+
* Initiate a search.
|
|
1275
|
+
*/ find(callbackOrText, findAttributes) {
|
|
1179
1276
|
this._isSearchActive = true;
|
|
1180
1277
|
this.editor.execute('find', callbackOrText, findAttributes);
|
|
1181
1278
|
return this.state.results;
|
|
1182
1279
|
}
|
|
1183
1280
|
/**
|
|
1184
|
-
|
|
1185
|
-
|
|
1281
|
+
* Stops active results from updating, and clears out the results.
|
|
1282
|
+
*/ stop() {
|
|
1186
1283
|
this.state.clear(this.editor.model);
|
|
1187
1284
|
this._isSearchActive = false;
|
|
1188
1285
|
}
|
|
1189
1286
|
/**
|
|
1190
|
-
|
|
1191
|
-
|
|
1287
|
+
* Sets up the commands.
|
|
1288
|
+
*/ _defineCommands() {
|
|
1192
1289
|
this.editor.commands.add('find', new FindCommand(this.editor, this.state));
|
|
1193
1290
|
this.editor.commands.add('findNext', new FindNextCommand(this.editor, this.state));
|
|
1194
1291
|
this.editor.commands.add('findPrevious', new FindPreviousCommand(this.editor, this.state));
|
|
@@ -1196,8 +1293,8 @@ class FindAndReplaceEditing extends Plugin {
|
|
|
1196
1293
|
this.editor.commands.add('replaceAll', new ReplaceAllCommand(this.editor, this.state));
|
|
1197
1294
|
}
|
|
1198
1295
|
/**
|
|
1199
|
-
|
|
1200
|
-
|
|
1296
|
+
* Sets up the marker downcast converters for search results highlighting.
|
|
1297
|
+
*/ _defineConverters() {
|
|
1201
1298
|
const { editor } = this;
|
|
1202
1299
|
// Setup the marker highlighting conversion.
|
|
1203
1300
|
editor.conversion.for('editingDowncast').markerToHighlight({
|
|
@@ -1237,99 +1334,105 @@ class FindAndReplaceEditing extends Plugin {
|
|
|
1237
1334
|
}
|
|
1238
1335
|
});
|
|
1239
1336
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1337
|
+
/**
|
|
1338
|
+
* Reacts to document changes in order to update search list.
|
|
1339
|
+
*/ _onDocumentChange = ()=>{
|
|
1340
|
+
const changedNodes = new Set();
|
|
1341
|
+
const removedMarkers = new Set();
|
|
1342
|
+
const model = this.editor.model;
|
|
1343
|
+
const { results } = this.state;
|
|
1344
|
+
const changes = model.document.differ.getChanges();
|
|
1345
|
+
const changedMarkers = model.document.differ.getChangedMarkers();
|
|
1346
|
+
// Get nodes in which changes happened to re-run a search callback on them.
|
|
1347
|
+
changes.forEach((change)=>{
|
|
1348
|
+
if (!change.position) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (change.name === '$text' || change.position.nodeAfter && model.schema.isInline(change.position.nodeAfter)) {
|
|
1352
|
+
changedNodes.add(change.position.parent);
|
|
1353
|
+
[
|
|
1354
|
+
...model.markers.getMarkersAtPosition(change.position)
|
|
1355
|
+
].forEach((markerAtChange)=>{
|
|
1356
|
+
removedMarkers.add(markerAtChange.name);
|
|
1357
|
+
});
|
|
1358
|
+
} else if (change.type === 'insert' && change.position.nodeAfter) {
|
|
1359
|
+
changedNodes.add(change.position.nodeAfter);
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
// Get markers from removed nodes also.
|
|
1363
|
+
changedMarkers.forEach(({ name, data: { newRange } })=>{
|
|
1364
|
+
if (newRange && newRange.start.root.rootName === '$graveyard') {
|
|
1365
|
+
removedMarkers.add(name);
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
// Get markers from the updated nodes and remove all (search will be re-run on these nodes).
|
|
1369
|
+
changedNodes.forEach((node)=>{
|
|
1370
|
+
const markersInNode = [
|
|
1371
|
+
...model.markers.getMarkersIntersectingRange(model.createRangeIn(node))
|
|
1372
|
+
];
|
|
1373
|
+
markersInNode.forEach((marker)=>removedMarkers.add(marker.name));
|
|
1374
|
+
});
|
|
1375
|
+
// Remove results from the changed part of content.
|
|
1376
|
+
removedMarkers.forEach((markerName)=>{
|
|
1377
|
+
if (!results.has(markerName)) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (results.get(markerName) === this.state.highlightedResult) {
|
|
1381
|
+
this.state.highlightedResult = null;
|
|
1382
|
+
}
|
|
1383
|
+
results.remove(markerName);
|
|
1384
|
+
});
|
|
1385
|
+
// Run search callback again on updated nodes.
|
|
1386
|
+
const changedSearchResults = [];
|
|
1387
|
+
const findAndReplaceUtils = this.editor.plugins.get('FindAndReplaceUtils');
|
|
1388
|
+
changedNodes.forEach((nodeToCheck)=>{
|
|
1389
|
+
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
|
|
1390
|
+
changedSearchResults.push(...changedNodeSearchResults);
|
|
1391
|
+
});
|
|
1392
|
+
changedMarkers.forEach((markerToCheck)=>{
|
|
1393
|
+
// Handle search result highlight update when T&C plugin is active.
|
|
1394
|
+
// Lookup is performed only on newly inserted markers.
|
|
1395
|
+
if (markerToCheck.data.newRange) {
|
|
1396
|
+
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
|
|
1295
1397
|
changedSearchResults.push(...changedNodeSearchResults);
|
|
1296
|
-
});
|
|
1297
|
-
changedMarkers.forEach((markerToCheck)=>{
|
|
1298
|
-
// Handle search result highlight update when T&C plugin is active.
|
|
1299
|
-
// Lookup is performed only on newly inserted markers.
|
|
1300
|
-
if (markerToCheck.data.newRange) {
|
|
1301
|
-
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
|
|
1302
|
-
changedSearchResults.push(...changedNodeSearchResults);
|
|
1303
|
-
}
|
|
1304
|
-
});
|
|
1305
|
-
if (!this.state.highlightedResult && changedSearchResults.length) {
|
|
1306
|
-
// If there are found phrases but none is selected, select the first one.
|
|
1307
|
-
this.state.highlightedResult = changedSearchResults[0];
|
|
1308
|
-
} else {
|
|
1309
|
-
// If there is already highlight item then refresh highlight offset after appending new items.
|
|
1310
|
-
this.state.refreshHighlightOffset();
|
|
1311
1398
|
}
|
|
1312
|
-
};
|
|
1313
|
-
|
|
1399
|
+
});
|
|
1400
|
+
if (!this.state.highlightedResult && changedSearchResults.length) {
|
|
1401
|
+
// If there are found phrases but none is selected, select the first one.
|
|
1402
|
+
this.state.highlightedResult = changedSearchResults[0];
|
|
1403
|
+
} else {
|
|
1404
|
+
// If there is already highlight item then refresh highlight offset after appending new items.
|
|
1405
|
+
this.state.refreshHighlightOffset();
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1314
1408
|
}
|
|
1315
1409
|
|
|
1316
|
-
|
|
1410
|
+
/**
|
|
1411
|
+
* The find and replace plugin.
|
|
1412
|
+
*
|
|
1413
|
+
* For a detailed overview, check the {@glink features/find-and-replace Find and replace feature documentation}.
|
|
1414
|
+
*
|
|
1415
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
1416
|
+
*
|
|
1417
|
+
* * The {@link module:find-and-replace/findandreplaceediting~FindAndReplaceEditing find and replace editing feature},
|
|
1418
|
+
* * The {@link module:find-and-replace/findandreplaceui~FindAndReplaceUI find and replace UI feature}
|
|
1419
|
+
*/ class FindAndReplace extends Plugin {
|
|
1317
1420
|
/**
|
|
1318
|
-
|
|
1319
|
-
|
|
1421
|
+
* @inheritDoc
|
|
1422
|
+
*/ static get requires() {
|
|
1320
1423
|
return [
|
|
1321
1424
|
FindAndReplaceEditing,
|
|
1322
1425
|
FindAndReplaceUI
|
|
1323
1426
|
];
|
|
1324
1427
|
}
|
|
1325
1428
|
/**
|
|
1326
|
-
|
|
1327
|
-
|
|
1429
|
+
* @inheritDoc
|
|
1430
|
+
*/ static get pluginName() {
|
|
1328
1431
|
return 'FindAndReplace';
|
|
1329
1432
|
}
|
|
1330
1433
|
/**
|
|
1331
|
-
|
|
1332
|
-
|
|
1434
|
+
* @inheritDoc
|
|
1435
|
+
*/ init() {
|
|
1333
1436
|
const ui = this.editor.plugins.get('FindAndReplaceUI');
|
|
1334
1437
|
const findAndReplaceEditing = this.editor.plugins.get('FindAndReplaceEditing');
|
|
1335
1438
|
const state = findAndReplaceEditing.state;
|