@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/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, ViewCollection, FocusCycler, Dialog, DropdownView, createDropdown, FormHeaderView, MenuBarMenuListItemButtonView, DialogViewPosition, CssTransitionDisablerMixin } from '@ckeditor/ckeditor5-ui/dist/index.js';
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
- class FindAndReplaceFormView extends View {
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
- * @inheritDoc
13
- */ render() {
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
- * @inheritDoc
23
- */ destroy() {
164
+ * @inheritDoc
165
+ */ destroy() {
24
166
  super.destroy();
25
167
  this._focusTracker.destroy();
26
168
  this._keystrokes.destroy();
27
169
  }
28
170
  /**
29
- * @inheritDoc
30
- */ focus(direction) {
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
- * Resets the form before re-appearing.
39
- *
40
- * It clears error messages, hides the match counter and disables the replace feature
41
- * until the next hit of the "Find" button.
42
- *
43
- * **Note**: It does not reset inputs and options, though. This way the form works better in editors with
44
- * disappearing toolbar (e.g. BalloonEditor): hiding the toolbar by accident (together with the find and replace UI)
45
- * does not require filling the entire form again.
46
- */ reset() {
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
- * Returns the value of the find input.
52
- */ get _textToFind() {
193
+ * Returns the value of the find input.
194
+ */ get _textToFind() {
53
195
  return this._findInputView.fieldView.element.value;
54
196
  }
55
197
  /**
56
- * Returns the value of the replace input.
57
- */ get _textToReplace() {
198
+ * Returns the value of the replace input.
199
+ */ get _textToReplace() {
58
200
  return this._replaceInputView.fieldView.element.value;
59
201
  }
60
202
  /**
61
- * Configures and returns the `<div>` aggregating all form inputs.
62
- */ _createInputsDiv() {
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
- * The action performed when the {@link #_findButtonView} is pressed.
103
- */ _onFindButtonExecute() {
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
- * Configures an injects the find results counter displaying a "N of M" label of the {@link #_findInputView}.
121
- */ _injectFindResultsCounter() {
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
- * Creates the collapsible view aggregating the advanced search options.
179
- */ _createAdvancedOptionsCollapsible() {
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
- * Configures and returns the `<div>` element aggregating all form action buttons.
193
- */ _createActionButtonsDiv() {
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
- * Creates, configures and returns and instance of a dropdown allowing users to narrow
229
- * the search criteria down. The dropdown has a list with switch buttons for each option.
230
- */ _createMatchCaseSwitch() {
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
- * Creates, configures and returns and instance of a dropdown allowing users to narrow
250
- * the search criteria down. The dropdown has a list with switch buttons for each option.
251
- */ _createWholeWordsOnlySwitch() {
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
- * Initializes the {@link #_focusables} and {@link #_focusTracker} to allow navigation
271
- * using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keystrokes in the right order.
272
- */ _initFocusCycling() {
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
- * Initializes the keystroke handling in the form.
294
- */ _initKeystrokeHandling() {
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
- * Creates a button view.
350
- *
351
- * @param options The properties of the `ButtonView`.
352
- * @returns The button view instance.
353
- */ _createButton(options) {
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
- * Creates a labeled input view.
360
- *
361
- * @param label The input label.
362
- * @returns The labeled input view instance.
363
- */ _createInputField(label, className) {
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
- class FindAndReplaceUI extends Plugin {
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
- * @inheritDoc
461
- */ static get requires() {
522
+ * @inheritDoc
523
+ */ static get requires() {
462
524
  return [
463
525
  Dialog
464
526
  ];
465
527
  }
466
528
  /**
467
- * @inheritDoc
468
- */ static get pluginName() {
529
+ * @inheritDoc
530
+ */ static get pluginName() {
469
531
  return 'FindAndReplaceUI';
470
532
  }
471
533
  /**
472
- * @inheritDoc
473
- */ init() {
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
- * Creates a dropdown containing the find and replace form.
529
- */ _createDropdown() {
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
- * Creates a button that opens a dialog with the find and replace form.
567
- */ _createDialogButtonForToolbar() {
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
- * Creates a button for for menu bar that will show find and replace dialog.
591
- */ _createDialogButtonForMenuBar() {
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
- * Creates a button for find and replace command to use either in toolbar or in menu bar.
605
- */ _createButton(ButtonClass) {
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
- * Shows the find and replace dialog.
621
- */ _showDialog() {
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
- * Sets up the form view for the find and replace.
643
- *
644
- * @param formView A related form view.
645
- */ _createFormView() {
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
- * Clears the find and replace form and focuses the search text field.
682
- */ _setupFormView() {
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
- * @inheritDoc
690
- */ constructor(editor){
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
- editor.config.define('findAndReplace.uiType', 'dialog');
693
- this.formView = null;
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
- class FindCommand extends Command {
698
- /**
699
- * Executes the command.
700
- *
701
- * @param callbackOrText
702
- * @param options Options object.
703
- * @param options.matchCase If set to `true`, the letter case will be matched.
704
- * @param options.wholeWords If set to `true`, only whole words that match `callbackOrText` will be matched.
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
- * Creates a new `FindCommand` instance.
742
- *
743
- * @param editor The editor on which this command will be used.
744
- * @param state An object to hold plugin state.
745
- */ constructor(editor, state){
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 find command is always enabled.
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
- * Common logic for both `replace` commands.
758
- * Replace a given find result by a string or a callback.
759
- *
760
- * @param result A single result from the find command.
761
- */ _replace(replacementText, result) {
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
- class ReplaceCommand extends ReplaceCommandBase {
803
- /**
804
- * Replace a given find result by a string or a callback.
805
- *
806
- * @param result A single result from the find command.
807
- *
808
- * @fires execute
809
- */ execute(replacementText, result) {
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
- class ReplaceAllCommand extends ReplaceCommandBase {
815
- /**
816
- * Replaces all the occurrences of `textToReplace` with a given `newText` string.
817
- *
818
- * ```ts
819
- * replaceAllCommand.execute( 'replaceAll', 'new text replacement', 'text to replace' );
820
- * ```
821
- *
822
- * Alternatively you can call it from editor instance:
823
- *
824
- * ```ts
825
- * editor.execute( 'replaceAll', 'new text', 'old text' );
826
- * ```
827
- *
828
- * @param newText Text that will be inserted to the editor for each match.
829
- * @param textToReplace Text to be replaced or a collection of matches
830
- * as returned by the find command.
831
- *
832
- * @fires module:core/command~Command#event:execute
833
- */ execute(newText, textToReplace) {
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
- class FindNextCommand extends Command {
853
- /**
854
- * @inheritDoc
855
- */ refresh() {
856
- this.isEnabled = this._state.results.length > 1;
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
- * @inheritDoc
860
- */ execute() {
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
- * Creates a new `FindNextCommand` instance.
868
- *
869
- * @param editor The editor on which this command will be used.
870
- * @param state An object to hold plugin state.
871
- */ constructor(editor, state){
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
- class FindPreviousCommand extends FindNextCommand {
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
- * @inheritDoc
886
- */ execute() {
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
- class FindAndReplaceState extends ObservableMixin() {
895
- /**
896
- * Cleans the state up and removes markers from the model.
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
- * Creates an instance of the state.
933
- */ constructor(model){
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
- class FindAndReplaceUtils extends Plugin {
1058
+ /**
1059
+ * A set of helpers related to find and replace.
1060
+ */ class FindAndReplaceUtils extends Plugin {
969
1061
  /**
970
- * @inheritDoc
971
- */ static get pluginName() {
1062
+ * @inheritDoc
1063
+ */ static get pluginName() {
972
1064
  return 'FindAndReplaceUtils';
973
1065
  }
974
1066
  /**
975
- * Executes findCallback and updates search results list.
976
- *
977
- * @param range The model range to scan for matches.
978
- * @param model The model.
979
- * @param findCallback The callback that should return `true` if provided text matches the search term.
980
- * @param startResults An optional collection of find matches that the function should
981
- * start with. This would be a collection returned by a previous `updateFindResultFromRange()` call.
982
- * @returns A collection of objects describing find match.
983
- *
984
- * An example structure:
985
- *
986
- * ```js
987
- * {
988
- * id: resultId,
989
- * label: foundItem.label,
990
- * marker
991
- * }
992
- * ```
993
- */ updateFindResultFromRange(range, model, findCallback, startResults) {
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
- * Returns text representation of a range. The returned text length should be the same as range length.
1038
- * In order to achieve this, this function will replace inline elements (text-line) as new line character ("\n").
1039
- *
1040
- * @param range The model range.
1041
- * @returns The text content of the provided range.
1042
- */ rangeToText(range) {
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
- * Creates a text matching callback for a specified search term and matching options.
1055
- *
1056
- * @param searchTerm The search term.
1057
- * @param options Matching options.
1058
- * - options.matchCase=false If set to `true` letter casing will be ignored.
1059
- * - options.wholeWords=false If set to `true` only whole words that match `callbackOrText` will be matched.
1060
- */ findByTextCallback(searchTerm, options) {
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
- class FindAndReplaceEditing extends Plugin {
1202
+ /**
1203
+ * Implements the editing part for find and replace plugin. For example conversion, commands etc.
1204
+ */ class FindAndReplaceEditing extends Plugin {
1111
1205
  /**
1112
- * @inheritDoc
1113
- */ static get requires() {
1206
+ * @inheritDoc
1207
+ */ static get requires() {
1114
1208
  return [
1115
1209
  FindAndReplaceUtils
1116
1210
  ];
1117
1211
  }
1118
1212
  /**
1119
- * @inheritDoc
1120
- */ static get pluginName() {
1213
+ * @inheritDoc
1214
+ */ static get pluginName() {
1121
1215
  return 'FindAndReplaceEditing';
1122
1216
  }
1123
1217
  /**
1124
- * @inheritDoc
1125
- */ init() {
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
- * Initiate a search.
1178
- */ find(callbackOrText, findAttributes) {
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
- * Stops active results from updating, and clears out the results.
1185
- */ stop() {
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
- * Sets up the commands.
1191
- */ _defineCommands() {
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
- * Sets up the marker downcast converters for search results highlighting.
1200
- */ _defineConverters() {
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
- constructor(){
1241
- super(...arguments);
1242
- /**
1243
- * Reacts to document changes in order to update search list.
1244
- */ this._onDocumentChange = ()=>{
1245
- const changedNodes = new Set();
1246
- const removedMarkers = new Set();
1247
- const model = this.editor.model;
1248
- const { results } = this.state;
1249
- const changes = model.document.differ.getChanges();
1250
- const changedMarkers = model.document.differ.getChangedMarkers();
1251
- // Get nodes in which changes happened to re-run a search callback on them.
1252
- changes.forEach((change)=>{
1253
- if (!change.position) {
1254
- return;
1255
- }
1256
- if (change.name === '$text' || change.position.nodeAfter && model.schema.isInline(change.position.nodeAfter)) {
1257
- changedNodes.add(change.position.parent);
1258
- [
1259
- ...model.markers.getMarkersAtPosition(change.position)
1260
- ].forEach((markerAtChange)=>{
1261
- removedMarkers.add(markerAtChange.name);
1262
- });
1263
- } else if (change.type === 'insert' && change.position.nodeAfter) {
1264
- changedNodes.add(change.position.nodeAfter);
1265
- }
1266
- });
1267
- // Get markers from removed nodes also.
1268
- changedMarkers.forEach(({ name, data: { newRange } })=>{
1269
- if (newRange && newRange.start.root.rootName === '$graveyard') {
1270
- removedMarkers.add(name);
1271
- }
1272
- });
1273
- // Get markers from the updated nodes and remove all (search will be re-run on these nodes).
1274
- changedNodes.forEach((node)=>{
1275
- const markersInNode = [
1276
- ...model.markers.getMarkersIntersectingRange(model.createRangeIn(node))
1277
- ];
1278
- markersInNode.forEach((marker)=>removedMarkers.add(marker.name));
1279
- });
1280
- // Remove results from the changed part of content.
1281
- removedMarkers.forEach((markerName)=>{
1282
- if (!results.has(markerName)) {
1283
- return;
1284
- }
1285
- if (results.get(markerName) === this.state.highlightedResult) {
1286
- this.state.highlightedResult = null;
1287
- }
1288
- results.remove(markerName);
1289
- });
1290
- // Run search callback again on updated nodes.
1291
- const changedSearchResults = [];
1292
- const findAndReplaceUtils = this.editor.plugins.get('FindAndReplaceUtils');
1293
- changedNodes.forEach((nodeToCheck)=>{
1294
- const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
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
- class FindAndReplace extends Plugin {
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
- * @inheritDoc
1319
- */ static get requires() {
1421
+ * @inheritDoc
1422
+ */ static get requires() {
1320
1423
  return [
1321
1424
  FindAndReplaceEditing,
1322
1425
  FindAndReplaceUI
1323
1426
  ];
1324
1427
  }
1325
1428
  /**
1326
- * @inheritDoc
1327
- */ static get pluginName() {
1429
+ * @inheritDoc
1430
+ */ static get pluginName() {
1328
1431
  return 'FindAndReplace';
1329
1432
  }
1330
1433
  /**
1331
- * @inheritDoc
1332
- */ init() {
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;