@ckeditor/ckeditor5-find-and-replace 48.2.0 → 48.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/augmentation.d.ts +26 -26
- package/dist/findandreplace.d.ts +39 -39
- package/dist/findandreplaceconfig.d.ts +26 -26
- package/dist/findandreplaceediting.d.ts +71 -71
- package/dist/findandreplacestate.d.ts +102 -105
- package/dist/findandreplaceui.d.ts +73 -73
- package/dist/findandreplaceutils.d.ts +68 -68
- package/dist/findcommand.d.ts +39 -39
- package/dist/findnextcommand.d.ts +29 -29
- package/dist/findpreviouscommand.d.ts +13 -13
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.js +1396 -1536
- package/dist/index.js.map +1 -1
- package/dist/replaceallcommand.d.ts +30 -30
- package/dist/replacecommand.d.ts +16 -16
- package/dist/replacecommandbase.d.ts +26 -26
- package/dist/ui/findandreplaceformview.d.ts +284 -284
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -2,1570 +2,1430 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { FocusTracker, KeystrokeHandler,
|
|
9
|
-
import {
|
|
5
|
+
import { Command, Plugin } from "@ckeditor/ckeditor5-core";
|
|
6
|
+
import { IconFindReplace, IconPreviousArrow } from "@ckeditor/ckeditor5-icons";
|
|
7
|
+
import { ButtonView, CollapsibleView, CssTransitionDisablerMixin, Dialog, DialogViewPosition, DropdownView, FocusCycler, FormHeaderView, LabeledFieldView, MenuBarMenuListItemButtonView, SwitchButtonView, View, ViewCollection, createDropdown, createLabeledInputText, submitHandler } from "@ckeditor/ckeditor5-ui";
|
|
8
|
+
import { Collection, FocusTracker, KeystrokeHandler, ObservableMixin, Rect, isVisible, scrollViewportToShowTarget, uid } from "@ckeditor/ckeditor5-utils";
|
|
9
|
+
import { debounce, escapeRegExp } from "es-toolkit/compat";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
// Find previous upon pressing Shift+Enter in the find field.
|
|
471
|
-
this._keystrokes.set('shift+enter', (event)=>{
|
|
472
|
-
const target = event.target;
|
|
473
|
-
if (target !== this._findInputView.fieldView.element) {
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (this._areCommandsEnabled.findPrevious) {
|
|
477
|
-
this._findPrevButtonView.fire('execute');
|
|
478
|
-
} else {
|
|
479
|
-
this._findButtonView.fire('execute');
|
|
480
|
-
}
|
|
481
|
-
stopPropagationAndPreventDefault(event);
|
|
482
|
-
});
|
|
483
|
-
// Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
|
|
484
|
-
// keystroke handler would take over the key management in the URL input.
|
|
485
|
-
// We need to prevent this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
|
|
486
|
-
this._keystrokes.set('arrowright', stopPropagation);
|
|
487
|
-
this._keystrokes.set('arrowleft', stopPropagation);
|
|
488
|
-
this._keystrokes.set('arrowup', stopPropagation);
|
|
489
|
-
this._keystrokes.set('arrowdown', stopPropagation);
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Creates a button view.
|
|
493
|
-
*
|
|
494
|
-
* @param options The properties of the `ButtonView`.
|
|
495
|
-
* @returns The button view instance.
|
|
496
|
-
*/ _createButton(options) {
|
|
497
|
-
const button = new ButtonView(this.locale);
|
|
498
|
-
button.set(options);
|
|
499
|
-
return button;
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Creates a labeled input view.
|
|
503
|
-
*
|
|
504
|
-
* @param label The input label.
|
|
505
|
-
* @returns The labeled input view instance.
|
|
506
|
-
*/ _createInputField(label, className) {
|
|
507
|
-
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
|
508
|
-
labeledInput.label = label;
|
|
509
|
-
labeledInput.class = className;
|
|
510
|
-
return labeledInput;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
12
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
13
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @module find-and-replace/ui/findandreplaceformview
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The find and replace form view class.
|
|
20
|
+
*
|
|
21
|
+
* See {@link module:find-and-replace/ui/findandreplaceformview~FindAndReplaceFormView}.
|
|
22
|
+
*/
|
|
23
|
+
var FindAndReplaceFormView = class extends View {
|
|
24
|
+
/**
|
|
25
|
+
* A collection of child views.
|
|
26
|
+
*/
|
|
27
|
+
children;
|
|
28
|
+
/**
|
|
29
|
+
* The find in text input view that stores the searched string.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
_findInputView;
|
|
34
|
+
/**
|
|
35
|
+
* The replace input view.
|
|
36
|
+
*/
|
|
37
|
+
_replaceInputView;
|
|
38
|
+
/**
|
|
39
|
+
* The find button view that initializes the search process.
|
|
40
|
+
*/
|
|
41
|
+
_findButtonView;
|
|
42
|
+
/**
|
|
43
|
+
* The find previous button view.
|
|
44
|
+
*/
|
|
45
|
+
_findPrevButtonView;
|
|
46
|
+
/**
|
|
47
|
+
* The find next button view.
|
|
48
|
+
*/
|
|
49
|
+
_findNextButtonView;
|
|
50
|
+
/**
|
|
51
|
+
* A collapsible view aggregating the advanced search options.
|
|
52
|
+
*/
|
|
53
|
+
_advancedOptionsCollapsibleView;
|
|
54
|
+
/**
|
|
55
|
+
* A switch button view controlling the "Match case" option.
|
|
56
|
+
*/
|
|
57
|
+
_matchCaseSwitchView;
|
|
58
|
+
/**
|
|
59
|
+
* A switch button view controlling the "Whole words only" option.
|
|
60
|
+
*/
|
|
61
|
+
_wholeWordsOnlySwitchView;
|
|
62
|
+
/**
|
|
63
|
+
* The replace button view.
|
|
64
|
+
*/
|
|
65
|
+
_replaceButtonView;
|
|
66
|
+
/**
|
|
67
|
+
* The replace all button view.
|
|
68
|
+
*/
|
|
69
|
+
_replaceAllButtonView;
|
|
70
|
+
/**
|
|
71
|
+
* The `div` aggregating the inputs.
|
|
72
|
+
*/
|
|
73
|
+
_inputsDivView;
|
|
74
|
+
/**
|
|
75
|
+
* The `div` aggregating the action buttons.
|
|
76
|
+
*/
|
|
77
|
+
_actionButtonsDivView;
|
|
78
|
+
/**
|
|
79
|
+
* Tracks information about the DOM focus in the form.
|
|
80
|
+
*/
|
|
81
|
+
_focusTracker;
|
|
82
|
+
/**
|
|
83
|
+
* An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
|
|
84
|
+
*/
|
|
85
|
+
_keystrokes;
|
|
86
|
+
/**
|
|
87
|
+
* A collection of views that can be focused in the form.
|
|
88
|
+
*/
|
|
89
|
+
_focusables;
|
|
90
|
+
/**
|
|
91
|
+
* Helps cycling over {@link #_focusables} in the form.
|
|
92
|
+
*/
|
|
93
|
+
focusCycler;
|
|
94
|
+
/**
|
|
95
|
+
* Creates a view of find and replace form.
|
|
96
|
+
*
|
|
97
|
+
* @param locale The localization services instance.
|
|
98
|
+
*/
|
|
99
|
+
constructor(locale) {
|
|
100
|
+
super(locale);
|
|
101
|
+
const t = locale.t;
|
|
102
|
+
this.children = this.createCollection();
|
|
103
|
+
this.set("matchCount", 0);
|
|
104
|
+
this.set("highlightOffset", 0);
|
|
105
|
+
this.set("isDirty", false);
|
|
106
|
+
this.set("_areCommandsEnabled", {});
|
|
107
|
+
this.set("_resultsCounterText", "");
|
|
108
|
+
this.set("_matchCase", false);
|
|
109
|
+
this.set("_wholeWordsOnly", false);
|
|
110
|
+
this.bind("_searchResultsFound").to(this, "matchCount", this, "isDirty", (matchCount, isDirty) => {
|
|
111
|
+
return matchCount > 0 && !isDirty;
|
|
112
|
+
});
|
|
113
|
+
this._findInputView = this._createInputField(t("Find in text…"));
|
|
114
|
+
this._findPrevButtonView = this._createButton({
|
|
115
|
+
label: t("Previous result"),
|
|
116
|
+
class: "ck-button-prev",
|
|
117
|
+
icon: IconPreviousArrow,
|
|
118
|
+
keystroke: "Shift+F3",
|
|
119
|
+
tooltip: true
|
|
120
|
+
});
|
|
121
|
+
this._findNextButtonView = this._createButton({
|
|
122
|
+
label: t("Next result"),
|
|
123
|
+
class: "ck-button-next",
|
|
124
|
+
icon: IconPreviousArrow,
|
|
125
|
+
keystroke: "F3",
|
|
126
|
+
tooltip: true
|
|
127
|
+
});
|
|
128
|
+
this._replaceInputView = this._createInputField(t("Replace with…"), "ck-labeled-field-replace");
|
|
129
|
+
this._inputsDivView = this._createInputsDiv();
|
|
130
|
+
this._matchCaseSwitchView = this._createMatchCaseSwitch();
|
|
131
|
+
this._wholeWordsOnlySwitchView = this._createWholeWordsOnlySwitch();
|
|
132
|
+
this._advancedOptionsCollapsibleView = this._createAdvancedOptionsCollapsible();
|
|
133
|
+
this._replaceAllButtonView = this._createButton({
|
|
134
|
+
label: t("Replace all"),
|
|
135
|
+
class: "ck-button-replaceall",
|
|
136
|
+
withText: true
|
|
137
|
+
});
|
|
138
|
+
this._replaceButtonView = this._createButton({
|
|
139
|
+
label: t("Replace"),
|
|
140
|
+
class: "ck-button-replace",
|
|
141
|
+
withText: true
|
|
142
|
+
});
|
|
143
|
+
this._findButtonView = this._createButton({
|
|
144
|
+
label: t("Find"),
|
|
145
|
+
class: "ck-button-find ck-button-action",
|
|
146
|
+
withText: true
|
|
147
|
+
});
|
|
148
|
+
this._actionButtonsDivView = this._createActionButtonsDiv();
|
|
149
|
+
this._focusTracker = new FocusTracker();
|
|
150
|
+
this._keystrokes = new KeystrokeHandler();
|
|
151
|
+
this._focusables = new ViewCollection();
|
|
152
|
+
this.focusCycler = new FocusCycler({
|
|
153
|
+
focusables: this._focusables,
|
|
154
|
+
focusTracker: this._focusTracker,
|
|
155
|
+
keystrokeHandler: this._keystrokes,
|
|
156
|
+
actions: {
|
|
157
|
+
focusPrevious: "shift + tab",
|
|
158
|
+
focusNext: "tab"
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
this.children.addMany([
|
|
162
|
+
this._inputsDivView,
|
|
163
|
+
this._advancedOptionsCollapsibleView,
|
|
164
|
+
this._actionButtonsDivView
|
|
165
|
+
]);
|
|
166
|
+
this.setTemplate({
|
|
167
|
+
tag: "form",
|
|
168
|
+
attributes: {
|
|
169
|
+
class: ["ck", "ck-find-and-replace-form"],
|
|
170
|
+
tabindex: "-1"
|
|
171
|
+
},
|
|
172
|
+
children: this.children
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @inheritDoc
|
|
177
|
+
*/
|
|
178
|
+
render() {
|
|
179
|
+
super.render();
|
|
180
|
+
submitHandler({ view: this });
|
|
181
|
+
this._initFocusCycling();
|
|
182
|
+
this._initKeystrokeHandling();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* @inheritDoc
|
|
186
|
+
*/
|
|
187
|
+
destroy() {
|
|
188
|
+
super.destroy();
|
|
189
|
+
this._focusTracker.destroy();
|
|
190
|
+
this._keystrokes.destroy();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* @inheritDoc
|
|
194
|
+
*/
|
|
195
|
+
focus(direction) {
|
|
196
|
+
if (direction === -1) this.focusCycler.focusLast();
|
|
197
|
+
else this.focusCycler.focusFirst();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resets the form before re-appearing.
|
|
201
|
+
*
|
|
202
|
+
* It clears error messages, hides the match counter and disables the replace feature
|
|
203
|
+
* until the next hit of the "Find" button.
|
|
204
|
+
*
|
|
205
|
+
* **Note**: It does not reset inputs and options, though. This way the form works better in editors with
|
|
206
|
+
* disappearing toolbar (e.g. BalloonEditor): hiding the toolbar by accident (together with the find and replace UI)
|
|
207
|
+
* does not require filling the entire form again.
|
|
208
|
+
*/
|
|
209
|
+
reset() {
|
|
210
|
+
this._findInputView.errorText = null;
|
|
211
|
+
this.isDirty = true;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Returns the value of the find input.
|
|
215
|
+
*/
|
|
216
|
+
get _textToFind() {
|
|
217
|
+
return this._findInputView.fieldView.element.value;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns the value of the replace input.
|
|
221
|
+
*/
|
|
222
|
+
get _textToReplace() {
|
|
223
|
+
return this._replaceInputView.fieldView.element.value;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Configures and returns the `<div>` aggregating all form inputs.
|
|
227
|
+
*/
|
|
228
|
+
_createInputsDiv() {
|
|
229
|
+
const locale = this.locale;
|
|
230
|
+
const t = locale.t;
|
|
231
|
+
const inputsDivView = new View(locale);
|
|
232
|
+
this._findInputView.fieldView.on("input", () => {
|
|
233
|
+
this.isDirty = true;
|
|
234
|
+
});
|
|
235
|
+
this._findPrevButtonView.delegate("execute").to(this, "findPrevious");
|
|
236
|
+
this._findNextButtonView.delegate("execute").to(this, "findNext");
|
|
237
|
+
this._findPrevButtonView.bind("isEnabled").to(this, "_areCommandsEnabled", ({ findPrevious }) => findPrevious);
|
|
238
|
+
this._findNextButtonView.bind("isEnabled").to(this, "_areCommandsEnabled", ({ findNext }) => findNext);
|
|
239
|
+
this._injectFindResultsCounter();
|
|
240
|
+
this._replaceInputView.bind("isEnabled").to(this, "_areCommandsEnabled", this, "_searchResultsFound", ({ replace }, resultsFound) => replace && resultsFound);
|
|
241
|
+
this._replaceInputView.bind("infoText").to(this._replaceInputView, "isEnabled", this._replaceInputView, "isFocused", (isEnabled, isFocused) => {
|
|
242
|
+
if (isEnabled || !isFocused) return "";
|
|
243
|
+
return t("Tip: Find some text first in order to replace it.");
|
|
244
|
+
});
|
|
245
|
+
inputsDivView.setTemplate({
|
|
246
|
+
tag: "div",
|
|
247
|
+
attributes: { class: ["ck", "ck-find-and-replace-form__inputs"] },
|
|
248
|
+
children: [
|
|
249
|
+
this._findInputView,
|
|
250
|
+
this._findPrevButtonView,
|
|
251
|
+
this._findNextButtonView,
|
|
252
|
+
this._replaceInputView
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
return inputsDivView;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The action performed when the {@link #_findButtonView} is pressed.
|
|
259
|
+
*/
|
|
260
|
+
_onFindButtonExecute() {
|
|
261
|
+
if (!this._textToFind) {
|
|
262
|
+
const t = this.t;
|
|
263
|
+
this._findInputView.errorText = t("Text to find must not be empty.");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.isDirty = false;
|
|
267
|
+
this.fire("findNext", {
|
|
268
|
+
searchText: this._textToFind,
|
|
269
|
+
matchCase: this._matchCase,
|
|
270
|
+
wholeWords: this._wholeWordsOnly
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Configures an injects the find results counter displaying a "N of M" label of the {@link #_findInputView}.
|
|
275
|
+
*/
|
|
276
|
+
_injectFindResultsCounter() {
|
|
277
|
+
const locale = this.locale;
|
|
278
|
+
const t = locale.t;
|
|
279
|
+
const bind = this.bindTemplate;
|
|
280
|
+
const resultsCounterView = new View(this.locale);
|
|
281
|
+
this.bind("_resultsCounterText").to(this, "highlightOffset", this, "matchCount", (highlightOffset, matchCount) => t("%0 of %1", [highlightOffset, matchCount]));
|
|
282
|
+
resultsCounterView.setTemplate({
|
|
283
|
+
tag: "span",
|
|
284
|
+
attributes: { class: [
|
|
285
|
+
"ck",
|
|
286
|
+
"ck-results-counter",
|
|
287
|
+
bind.if("isDirty", "ck-hidden")
|
|
288
|
+
] },
|
|
289
|
+
children: [{ text: bind.to("_resultsCounterText") }]
|
|
290
|
+
});
|
|
291
|
+
const updateFindInputPadding = () => {
|
|
292
|
+
const inputElement = this._findInputView.fieldView.element;
|
|
293
|
+
if (!inputElement || !isVisible(inputElement)) return;
|
|
294
|
+
const counterWidth = new Rect(resultsCounterView.element).width;
|
|
295
|
+
const paddingPropertyName = locale.uiLanguageDirection === "ltr" ? "paddingRight" : "paddingLeft";
|
|
296
|
+
if (!counterWidth) inputElement.style[paddingPropertyName] = "";
|
|
297
|
+
else inputElement.style[paddingPropertyName] = `calc( 2 * var(--ck-spacing-standard) + ${counterWidth}px )`;
|
|
298
|
+
};
|
|
299
|
+
this.on("change:_resultsCounterText", updateFindInputPadding, { priority: "low" });
|
|
300
|
+
this.on("change:isDirty", updateFindInputPadding, { priority: "low" });
|
|
301
|
+
this._findInputView.template.children[0].children.push(resultsCounterView);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Creates the collapsible view aggregating the advanced search options.
|
|
305
|
+
*/
|
|
306
|
+
_createAdvancedOptionsCollapsible() {
|
|
307
|
+
const t = this.locale.t;
|
|
308
|
+
const collapsible = new CollapsibleView(this.locale, [this._matchCaseSwitchView, this._wholeWordsOnlySwitchView]);
|
|
309
|
+
collapsible.set({
|
|
310
|
+
label: t("Advanced options"),
|
|
311
|
+
isCollapsed: true
|
|
312
|
+
});
|
|
313
|
+
return collapsible;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Configures and returns the `<div>` element aggregating all form action buttons.
|
|
317
|
+
*/
|
|
318
|
+
_createActionButtonsDiv() {
|
|
319
|
+
const actionsDivView = new View(this.locale);
|
|
320
|
+
this._replaceButtonView.bind("isEnabled").to(this, "_areCommandsEnabled", this, "_searchResultsFound", ({ replace }, resultsFound) => replace && resultsFound);
|
|
321
|
+
this._replaceAllButtonView.bind("isEnabled").to(this, "_areCommandsEnabled", this, "_searchResultsFound", ({ replaceAll }, resultsFound) => replaceAll && resultsFound);
|
|
322
|
+
this._replaceButtonView.on("execute", () => {
|
|
323
|
+
this.fire("replace", {
|
|
324
|
+
searchText: this._textToFind,
|
|
325
|
+
replaceText: this._textToReplace
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
this._replaceAllButtonView.on("execute", () => {
|
|
329
|
+
this.fire("replaceAll", {
|
|
330
|
+
searchText: this._textToFind,
|
|
331
|
+
replaceText: this._textToReplace
|
|
332
|
+
});
|
|
333
|
+
this.focus();
|
|
334
|
+
});
|
|
335
|
+
this._findButtonView.on("execute", this._onFindButtonExecute.bind(this));
|
|
336
|
+
actionsDivView.setTemplate({
|
|
337
|
+
tag: "div",
|
|
338
|
+
attributes: { class: ["ck", "ck-find-and-replace-form__actions"] },
|
|
339
|
+
children: [
|
|
340
|
+
this._replaceAllButtonView,
|
|
341
|
+
this._replaceButtonView,
|
|
342
|
+
this._findButtonView
|
|
343
|
+
]
|
|
344
|
+
});
|
|
345
|
+
return actionsDivView;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Creates, configures and returns and instance of a dropdown allowing users to narrow
|
|
349
|
+
* the search criteria down. The dropdown has a list with switch buttons for each option.
|
|
350
|
+
*/
|
|
351
|
+
_createMatchCaseSwitch() {
|
|
352
|
+
const t = this.locale.t;
|
|
353
|
+
const matchCaseSwitchButton = new SwitchButtonView(this.locale);
|
|
354
|
+
matchCaseSwitchButton.set({
|
|
355
|
+
label: t("Match case"),
|
|
356
|
+
withText: true
|
|
357
|
+
});
|
|
358
|
+
matchCaseSwitchButton.bind("isOn").to(this, "_matchCase");
|
|
359
|
+
matchCaseSwitchButton.on("execute", () => {
|
|
360
|
+
this._matchCase = !this._matchCase;
|
|
361
|
+
this.isDirty = true;
|
|
362
|
+
});
|
|
363
|
+
return matchCaseSwitchButton;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Creates, configures and returns and instance of a dropdown allowing users to narrow
|
|
367
|
+
* the search criteria down. The dropdown has a list with switch buttons for each option.
|
|
368
|
+
*/
|
|
369
|
+
_createWholeWordsOnlySwitch() {
|
|
370
|
+
const t = this.locale.t;
|
|
371
|
+
const wholeWordsOnlySwitchButton = new SwitchButtonView(this.locale);
|
|
372
|
+
wholeWordsOnlySwitchButton.set({
|
|
373
|
+
label: t("Whole words only"),
|
|
374
|
+
withText: true
|
|
375
|
+
});
|
|
376
|
+
wholeWordsOnlySwitchButton.bind("isOn").to(this, "_wholeWordsOnly");
|
|
377
|
+
wholeWordsOnlySwitchButton.on("execute", () => {
|
|
378
|
+
this._wholeWordsOnly = !this._wholeWordsOnly;
|
|
379
|
+
this.isDirty = true;
|
|
380
|
+
});
|
|
381
|
+
return wholeWordsOnlySwitchButton;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Initializes the {@link #_focusables} and {@link #_focusTracker} to allow navigation
|
|
385
|
+
* using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keystrokes in the right order.
|
|
386
|
+
*/
|
|
387
|
+
_initFocusCycling() {
|
|
388
|
+
[
|
|
389
|
+
this._findInputView,
|
|
390
|
+
this._findPrevButtonView,
|
|
391
|
+
this._findNextButtonView,
|
|
392
|
+
this._replaceInputView,
|
|
393
|
+
this._advancedOptionsCollapsibleView.buttonView,
|
|
394
|
+
this._matchCaseSwitchView,
|
|
395
|
+
this._wholeWordsOnlySwitchView,
|
|
396
|
+
this._replaceAllButtonView,
|
|
397
|
+
this._replaceButtonView,
|
|
398
|
+
this._findButtonView
|
|
399
|
+
].forEach((v) => {
|
|
400
|
+
this._focusables.add(v);
|
|
401
|
+
this._focusTracker.add(v.element);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Initializes the keystroke handling in the form.
|
|
406
|
+
*/
|
|
407
|
+
_initKeystrokeHandling() {
|
|
408
|
+
const stopPropagation = (data) => data.stopPropagation();
|
|
409
|
+
const stopPropagationAndPreventDefault = (data) => {
|
|
410
|
+
data.stopPropagation();
|
|
411
|
+
data.preventDefault();
|
|
412
|
+
};
|
|
413
|
+
this._keystrokes.listenTo(this.element);
|
|
414
|
+
this._keystrokes.set("f3", (event) => {
|
|
415
|
+
stopPropagationAndPreventDefault(event);
|
|
416
|
+
this._findNextButtonView.fire("execute");
|
|
417
|
+
});
|
|
418
|
+
this._keystrokes.set("shift+f3", (event) => {
|
|
419
|
+
stopPropagationAndPreventDefault(event);
|
|
420
|
+
this._findPrevButtonView.fire("execute");
|
|
421
|
+
});
|
|
422
|
+
this._keystrokes.set("enter", (event) => {
|
|
423
|
+
const target = event.target;
|
|
424
|
+
if (target === this._findInputView.fieldView.element) {
|
|
425
|
+
if (this._areCommandsEnabled.findNext) this._findNextButtonView.fire("execute");
|
|
426
|
+
else this._findButtonView.fire("execute");
|
|
427
|
+
stopPropagationAndPreventDefault(event);
|
|
428
|
+
} else if (target === this._replaceInputView.fieldView.element && !this.isDirty) {
|
|
429
|
+
this._replaceButtonView.fire("execute");
|
|
430
|
+
stopPropagationAndPreventDefault(event);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
this._keystrokes.set("shift+enter", (event) => {
|
|
434
|
+
if (event.target !== this._findInputView.fieldView.element) return;
|
|
435
|
+
if (this._areCommandsEnabled.findPrevious) this._findPrevButtonView.fire("execute");
|
|
436
|
+
else this._findButtonView.fire("execute");
|
|
437
|
+
stopPropagationAndPreventDefault(event);
|
|
438
|
+
});
|
|
439
|
+
this._keystrokes.set("arrowright", stopPropagation);
|
|
440
|
+
this._keystrokes.set("arrowleft", stopPropagation);
|
|
441
|
+
this._keystrokes.set("arrowup", stopPropagation);
|
|
442
|
+
this._keystrokes.set("arrowdown", stopPropagation);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Creates a button view.
|
|
446
|
+
*
|
|
447
|
+
* @param options The properties of the `ButtonView`.
|
|
448
|
+
* @returns The button view instance.
|
|
449
|
+
*/
|
|
450
|
+
_createButton(options) {
|
|
451
|
+
const button = new ButtonView(this.locale);
|
|
452
|
+
button.set(options);
|
|
453
|
+
return button;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Creates a labeled input view.
|
|
457
|
+
*
|
|
458
|
+
* @param label The input label.
|
|
459
|
+
* @returns The labeled input view instance.
|
|
460
|
+
*/
|
|
461
|
+
_createInputField(label, className) {
|
|
462
|
+
const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
|
|
463
|
+
labeledInput.label = label;
|
|
464
|
+
labeledInput.class = className;
|
|
465
|
+
return labeledInput;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
513
468
|
|
|
514
469
|
/**
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
*/ _createFormView() {
|
|
727
|
-
const editor = this.editor;
|
|
728
|
-
const formView = new (CssTransitionDisablerMixin(FindAndReplaceFormView))(editor.locale);
|
|
729
|
-
const commands = editor.commands;
|
|
730
|
-
const findAndReplaceEditing = this.editor.plugins.get('FindAndReplaceEditing');
|
|
731
|
-
const editingState = findAndReplaceEditing.state;
|
|
732
|
-
formView.bind('highlightOffset').to(editingState, 'highlightedOffset');
|
|
733
|
-
// Let the form know how many results were found in total.
|
|
734
|
-
formView.listenTo(editingState.results, 'change', ()=>{
|
|
735
|
-
formView.matchCount = editingState.results.length;
|
|
736
|
-
});
|
|
737
|
-
// Command states are used to enable/disable individual form controls.
|
|
738
|
-
// To keep things simple, instead of binding 4 individual observables, there's only one that combines every
|
|
739
|
-
// commands' isEnabled state. Yes, it will change more often but this simplifies the structure of the form.
|
|
740
|
-
const findNextCommand = commands.get('findNext');
|
|
741
|
-
const findPreviousCommand = commands.get('findPrevious');
|
|
742
|
-
const replaceCommand = commands.get('replace');
|
|
743
|
-
const replaceAllCommand = commands.get('replaceAll');
|
|
744
|
-
formView.bind('_areCommandsEnabled').to(findNextCommand, 'isEnabled', findPreviousCommand, 'isEnabled', replaceCommand, 'isEnabled', replaceAllCommand, 'isEnabled', (findNext, findPrevious, replace, replaceAll)=>({
|
|
745
|
-
findNext,
|
|
746
|
-
findPrevious,
|
|
747
|
-
replace,
|
|
748
|
-
replaceAll
|
|
749
|
-
}));
|
|
750
|
-
// The UI plugin works as an interface between the form and the editing part of the feature.
|
|
751
|
-
formView.delegate('findNext', 'findPrevious', 'replace', 'replaceAll').to(this);
|
|
752
|
-
// Let the feature know that search results are no longer relevant because the user changed the searched phrase
|
|
753
|
-
// (or options) but didn't hit the "Find" button yet (e.g. still typing).
|
|
754
|
-
formView.on('change:isDirty', (evt, data, isDirty)=>{
|
|
755
|
-
if (isDirty) {
|
|
756
|
-
this.fire('searchReseted');
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
return formView;
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Clears the find and replace form and focuses the search text field.
|
|
763
|
-
*/ _setupFormView() {
|
|
764
|
-
this.formView.disableCssTransitions();
|
|
765
|
-
this.formView.reset();
|
|
766
|
-
this.formView._findInputView.fieldView.select();
|
|
767
|
-
this.formView.enableCssTransitions();
|
|
768
|
-
}
|
|
769
|
-
}
|
|
470
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
471
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* @module find-and-replace/findandreplaceui
|
|
475
|
+
*/
|
|
476
|
+
/**
|
|
477
|
+
* The default find and replace UI.
|
|
478
|
+
*
|
|
479
|
+
* It registers the `'findAndReplace'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}.
|
|
480
|
+
* that uses the {@link module:find-and-replace/findandreplace~FindAndReplace FindAndReplace} plugin API.
|
|
481
|
+
*/
|
|
482
|
+
var FindAndReplaceUI = class extends Plugin {
|
|
483
|
+
/**
|
|
484
|
+
* @inheritDoc
|
|
485
|
+
*/
|
|
486
|
+
static get requires() {
|
|
487
|
+
return [Dialog];
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* @inheritDoc
|
|
491
|
+
*/
|
|
492
|
+
static get pluginName() {
|
|
493
|
+
return "FindAndReplaceUI";
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* @inheritDoc
|
|
497
|
+
*/
|
|
498
|
+
static get isOfficialPlugin() {
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* A reference to the find and replace form view.
|
|
503
|
+
*/
|
|
504
|
+
formView;
|
|
505
|
+
/**
|
|
506
|
+
* @inheritDoc
|
|
507
|
+
*/
|
|
508
|
+
constructor(editor) {
|
|
509
|
+
super(editor);
|
|
510
|
+
editor.config.define("findAndReplace.uiType", "dialog");
|
|
511
|
+
this.formView = null;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* @inheritDoc
|
|
515
|
+
*/
|
|
516
|
+
init() {
|
|
517
|
+
const editor = this.editor;
|
|
518
|
+
const isUiUsingDropdown = editor.config.get("findAndReplace.uiType") === "dropdown";
|
|
519
|
+
const findCommand = editor.commands.get("find");
|
|
520
|
+
const t = this.editor.t;
|
|
521
|
+
editor.ui.componentFactory.add("findAndReplace", () => {
|
|
522
|
+
let view;
|
|
523
|
+
if (isUiUsingDropdown) {
|
|
524
|
+
view = this._createDropdown();
|
|
525
|
+
view.bind("isEnabled").to(findCommand);
|
|
526
|
+
} else view = this._createDialogButtonForToolbar();
|
|
527
|
+
editor.keystrokes.set("Ctrl+F", (data, cancelEvent) => {
|
|
528
|
+
if (!findCommand.isEnabled) return;
|
|
529
|
+
if (view instanceof DropdownView) {
|
|
530
|
+
const dropdownButtonView = view.buttonView;
|
|
531
|
+
if (!dropdownButtonView.isOn) dropdownButtonView.fire("execute");
|
|
532
|
+
} else if (view.isOn) editor.plugins.get("Dialog").view.focus();
|
|
533
|
+
else view.fire("execute");
|
|
534
|
+
cancelEvent();
|
|
535
|
+
});
|
|
536
|
+
return view;
|
|
537
|
+
});
|
|
538
|
+
if (!isUiUsingDropdown) editor.ui.componentFactory.add("menuBar:findAndReplace", () => {
|
|
539
|
+
return this._createDialogButtonForMenuBar();
|
|
540
|
+
});
|
|
541
|
+
editor.accessibility.addKeystrokeInfos({ keystrokes: [{
|
|
542
|
+
label: t("Find in the document"),
|
|
543
|
+
keystroke: "CTRL+F"
|
|
544
|
+
}] });
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Creates a dropdown containing the find and replace form.
|
|
548
|
+
*/
|
|
549
|
+
_createDropdown() {
|
|
550
|
+
const editor = this.editor;
|
|
551
|
+
const t = editor.locale.t;
|
|
552
|
+
const dropdownView = createDropdown(editor.locale);
|
|
553
|
+
dropdownView.once("change:isOpen", () => {
|
|
554
|
+
this.formView = this._createFormView();
|
|
555
|
+
this.formView.children.add(new FormHeaderView(editor.locale, { label: t("Find and replace") }), 0);
|
|
556
|
+
dropdownView.panelView.children.add(this.formView);
|
|
557
|
+
});
|
|
558
|
+
dropdownView.on("change:isOpen", (event, name, isOpen) => {
|
|
559
|
+
if (isOpen) this._setupFormView();
|
|
560
|
+
else this.fire("searchReseted");
|
|
561
|
+
}, { priority: "low" });
|
|
562
|
+
dropdownView.buttonView.set({
|
|
563
|
+
icon: IconFindReplace,
|
|
564
|
+
label: t("Find and replace"),
|
|
565
|
+
keystroke: "CTRL+F",
|
|
566
|
+
tooltip: true
|
|
567
|
+
});
|
|
568
|
+
return dropdownView;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Creates a button that opens a dialog with the find and replace form.
|
|
572
|
+
*/
|
|
573
|
+
_createDialogButtonForToolbar() {
|
|
574
|
+
const editor = this.editor;
|
|
575
|
+
const buttonView = this._createButton(ButtonView);
|
|
576
|
+
const dialog = editor.plugins.get("Dialog");
|
|
577
|
+
buttonView.set({ tooltip: true });
|
|
578
|
+
buttonView.bind("isOn").to(dialog, "id", (id) => id === "findAndReplace");
|
|
579
|
+
buttonView.on("execute", () => {
|
|
580
|
+
if (buttonView.isOn) dialog.hide();
|
|
581
|
+
else this._showDialog();
|
|
582
|
+
});
|
|
583
|
+
return buttonView;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Creates a button for for menu bar that will show find and replace dialog.
|
|
587
|
+
*/
|
|
588
|
+
_createDialogButtonForMenuBar() {
|
|
589
|
+
const buttonView = this._createButton(MenuBarMenuListItemButtonView);
|
|
590
|
+
const dialogPlugin = this.editor.plugins.get("Dialog");
|
|
591
|
+
const dialog = this.editor.plugins.get("Dialog");
|
|
592
|
+
buttonView.set({
|
|
593
|
+
role: "menuitemcheckbox",
|
|
594
|
+
isToggleable: true
|
|
595
|
+
});
|
|
596
|
+
buttonView.bind("isOn").to(dialog, "id", (id) => id === "findAndReplace");
|
|
597
|
+
buttonView.on("execute", () => {
|
|
598
|
+
if (dialogPlugin.id === "findAndReplace") {
|
|
599
|
+
dialogPlugin.hide();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this._showDialog();
|
|
603
|
+
});
|
|
604
|
+
return buttonView;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Creates a button for find and replace command to use either in toolbar or in menu bar.
|
|
608
|
+
*/
|
|
609
|
+
_createButton(ButtonClass) {
|
|
610
|
+
const editor = this.editor;
|
|
611
|
+
const findCommand = editor.commands.get("find");
|
|
612
|
+
const buttonView = new ButtonClass(editor.locale);
|
|
613
|
+
const t = editor.locale.t;
|
|
614
|
+
buttonView.bind("isEnabled").to(findCommand);
|
|
615
|
+
buttonView.set({
|
|
616
|
+
icon: IconFindReplace,
|
|
617
|
+
label: t("Find and replace"),
|
|
618
|
+
keystroke: "CTRL+F"
|
|
619
|
+
});
|
|
620
|
+
return buttonView;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Shows the find and replace dialog.
|
|
624
|
+
*/
|
|
625
|
+
_showDialog() {
|
|
626
|
+
const editor = this.editor;
|
|
627
|
+
const dialog = editor.plugins.get("Dialog");
|
|
628
|
+
const t = editor.locale.t;
|
|
629
|
+
if (!this.formView) this.formView = this._createFormView();
|
|
630
|
+
dialog.show({
|
|
631
|
+
id: "findAndReplace",
|
|
632
|
+
title: t("Find and replace"),
|
|
633
|
+
content: this.formView,
|
|
634
|
+
position: DialogViewPosition.EDITOR_TOP_SIDE,
|
|
635
|
+
onShow: () => {
|
|
636
|
+
this._setupFormView();
|
|
637
|
+
},
|
|
638
|
+
onHide: () => {
|
|
639
|
+
this.fire("searchReseted");
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Sets up the form view for the findN and replace.
|
|
645
|
+
*/
|
|
646
|
+
_createFormView() {
|
|
647
|
+
const editor = this.editor;
|
|
648
|
+
const formView = new (CssTransitionDisablerMixin(FindAndReplaceFormView))(editor.locale);
|
|
649
|
+
const commands = editor.commands;
|
|
650
|
+
const editingState = this.editor.plugins.get("FindAndReplaceEditing").state;
|
|
651
|
+
formView.bind("highlightOffset").to(editingState, "highlightedOffset");
|
|
652
|
+
formView.listenTo(editingState.results, "change", () => {
|
|
653
|
+
formView.matchCount = editingState.results.length;
|
|
654
|
+
});
|
|
655
|
+
const findNextCommand = commands.get("findNext");
|
|
656
|
+
const findPreviousCommand = commands.get("findPrevious");
|
|
657
|
+
const replaceCommand = commands.get("replace");
|
|
658
|
+
const replaceAllCommand = commands.get("replaceAll");
|
|
659
|
+
formView.bind("_areCommandsEnabled").to(findNextCommand, "isEnabled", findPreviousCommand, "isEnabled", replaceCommand, "isEnabled", replaceAllCommand, "isEnabled", (findNext, findPrevious, replace, replaceAll) => ({
|
|
660
|
+
findNext,
|
|
661
|
+
findPrevious,
|
|
662
|
+
replace,
|
|
663
|
+
replaceAll
|
|
664
|
+
}));
|
|
665
|
+
formView.delegate("findNext", "findPrevious", "replace", "replaceAll").to(this);
|
|
666
|
+
formView.on("change:isDirty", (evt, data, isDirty) => {
|
|
667
|
+
if (isDirty) this.fire("searchReseted");
|
|
668
|
+
});
|
|
669
|
+
return formView;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Clears the find and replace form and focuses the search text field.
|
|
673
|
+
*/
|
|
674
|
+
_setupFormView() {
|
|
675
|
+
this.formView.disableCssTransitions();
|
|
676
|
+
this.formView.reset();
|
|
677
|
+
this.formView._findInputView.fieldView.select();
|
|
678
|
+
this.formView.enableCssTransitions();
|
|
679
|
+
}
|
|
680
|
+
};
|
|
770
681
|
|
|
771
682
|
/**
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
683
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
684
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
685
|
+
*/
|
|
686
|
+
/**
|
|
687
|
+
* @module find-and-replace/findcommand
|
|
688
|
+
*/
|
|
689
|
+
/**
|
|
690
|
+
* The find command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
691
|
+
*/
|
|
692
|
+
var FindCommand = class extends Command {
|
|
693
|
+
/**
|
|
694
|
+
* The find and replace state object used for command operations.
|
|
695
|
+
*/
|
|
696
|
+
_state;
|
|
697
|
+
/**
|
|
698
|
+
* Creates a new `FindCommand` instance.
|
|
699
|
+
*
|
|
700
|
+
* @param editor The editor on which this command will be used.
|
|
701
|
+
* @param state An object to hold plugin state.
|
|
702
|
+
*/
|
|
703
|
+
constructor(editor, state) {
|
|
704
|
+
super(editor);
|
|
705
|
+
this.isEnabled = true;
|
|
706
|
+
this.affectsData = false;
|
|
707
|
+
this._state = state;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Executes the command.
|
|
711
|
+
*
|
|
712
|
+
* @param callbackOrText
|
|
713
|
+
* @param options Options object.
|
|
714
|
+
* @param options.matchCase If set to `true`, the letter case will be matched.
|
|
715
|
+
* @param options.wholeWords If set to `true`, only whole words that match `callbackOrText` will be matched.
|
|
716
|
+
*
|
|
717
|
+
* @fires execute
|
|
718
|
+
*/
|
|
719
|
+
execute(callbackOrText, { matchCase, wholeWords } = {}) {
|
|
720
|
+
const { editor } = this;
|
|
721
|
+
const { model } = editor;
|
|
722
|
+
const findAndReplaceUtils = editor.plugins.get("FindAndReplaceUtils");
|
|
723
|
+
let findCallback;
|
|
724
|
+
let callbackSearchText = "";
|
|
725
|
+
if (typeof callbackOrText === "string") findCallback = (...args) => ({
|
|
726
|
+
results: findAndReplaceUtils.findByTextCallback(callbackOrText, {
|
|
727
|
+
matchCase,
|
|
728
|
+
wholeWords
|
|
729
|
+
})(...args),
|
|
730
|
+
searchText: callbackOrText
|
|
731
|
+
});
|
|
732
|
+
else findCallback = callbackOrText;
|
|
733
|
+
const oldCallback = findCallback;
|
|
734
|
+
findCallback = (...args) => {
|
|
735
|
+
const result = oldCallback(...args);
|
|
736
|
+
if (result && "searchText" in result) callbackSearchText = result.searchText;
|
|
737
|
+
return result;
|
|
738
|
+
};
|
|
739
|
+
const results = model.document.getRootNames().reduce(((currentResults, rootName) => findAndReplaceUtils.updateFindResultFromRange(model.createRangeIn(model.document.getRoot(rootName)), model, findCallback, currentResults)), null);
|
|
740
|
+
this._state.clear(model);
|
|
741
|
+
this._state.results.addMany(results);
|
|
742
|
+
this._state.highlightedResult = results.get(0);
|
|
743
|
+
this._state.searchText = callbackSearchText;
|
|
744
|
+
/* v8 ignore next -- @preserve */
|
|
745
|
+
if (findCallback) this._state.lastSearchCallback = findCallback;
|
|
746
|
+
this._state.matchCase = !!matchCase;
|
|
747
|
+
this._state.matchWholeWords = !!wholeWords;
|
|
748
|
+
return {
|
|
749
|
+
results,
|
|
750
|
+
findCallback
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
};
|
|
843
754
|
|
|
844
755
|
/**
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
this.highlightedOffset = 0;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
756
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
757
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
758
|
+
*/
|
|
759
|
+
const FindAndReplaceStateBase = /* #__PURE__ */ ObservableMixin();
|
|
760
|
+
/**
|
|
761
|
+
* The object storing find and replace plugin state for a given editor instance.
|
|
762
|
+
*/
|
|
763
|
+
var FindAndReplaceState = class extends FindAndReplaceStateBase {
|
|
764
|
+
/**
|
|
765
|
+
* Creates an instance of the state.
|
|
766
|
+
*/
|
|
767
|
+
constructor(model) {
|
|
768
|
+
super();
|
|
769
|
+
this.set("results", new Collection());
|
|
770
|
+
this.set("highlightedResult", null);
|
|
771
|
+
this.set("highlightedOffset", 0);
|
|
772
|
+
this.set("searchText", "");
|
|
773
|
+
this.set("replaceText", "");
|
|
774
|
+
this.set("lastSearchCallback", null);
|
|
775
|
+
this.set("matchCase", false);
|
|
776
|
+
this.set("matchWholeWords", false);
|
|
777
|
+
this.results.on("change", (eventInfo, { removed, index }) => {
|
|
778
|
+
if (Array.from(removed).length) {
|
|
779
|
+
let highlightedResultRemoved = false;
|
|
780
|
+
model.change((writer) => {
|
|
781
|
+
for (const removedResult of removed) {
|
|
782
|
+
if (this.highlightedResult === removedResult) highlightedResultRemoved = true;
|
|
783
|
+
if (model.markers.has(removedResult.marker.name)) writer.removeMarker(removedResult.marker);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
if (highlightedResultRemoved) {
|
|
787
|
+
const nextHighlightedIndex = index >= this.results.length ? 0 : index;
|
|
788
|
+
this.highlightedResult = this.results.get(nextHighlightedIndex);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
this.on("change:highlightedResult", () => {
|
|
793
|
+
this.refreshHighlightOffset(model);
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Cleans the state up and removes markers from the model.
|
|
798
|
+
*/
|
|
799
|
+
clear(model) {
|
|
800
|
+
this.searchText = "";
|
|
801
|
+
model.change((writer) => {
|
|
802
|
+
if (this.highlightedResult) {
|
|
803
|
+
const oldMatchId = this.highlightedResult.marker.name.split(":")[1];
|
|
804
|
+
const oldMarker = model.markers.get(`findResultHighlighted:${oldMatchId}`);
|
|
805
|
+
if (oldMarker) writer.removeMarker(oldMarker);
|
|
806
|
+
}
|
|
807
|
+
[...this.results].forEach(({ marker }) => {
|
|
808
|
+
writer.removeMarker(marker);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
this.results.clear();
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Refreshes the highlight result offset based on it's index within the result list.
|
|
815
|
+
*/
|
|
816
|
+
refreshHighlightOffset(model) {
|
|
817
|
+
const { highlightedResult, results } = this;
|
|
818
|
+
if (highlightedResult) this.highlightedOffset = sortSearchResultsByMarkerPositions(model, [...results]).indexOf(highlightedResult) + 1;
|
|
819
|
+
else this.highlightedOffset = 0;
|
|
820
|
+
}
|
|
821
|
+
};
|
|
915
822
|
/**
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
// See more: https://github.com/ckeditor/ckeditor5/pull/17292#issuecomment-2442084549
|
|
931
|
-
return model.document.getRootNames().flatMap((rootName)=>results.filter((result)=>result.marker.getStart().root.rootName === rootName).sort((a, b)=>sortMapping[a.marker.getStart().compareWith(b.marker.getStart())]));
|
|
823
|
+
* Sorts search results by marker positions. Make sure that the results are sorted in the same order as they appear in the document
|
|
824
|
+
* to avoid issues with the `find next` command. Apparently, the order of the results in the state might be different than the order
|
|
825
|
+
* of the markers in the model.
|
|
826
|
+
*
|
|
827
|
+
* @internal
|
|
828
|
+
*/
|
|
829
|
+
function sortSearchResultsByMarkerPositions(model, results) {
|
|
830
|
+
const sortMapping = {
|
|
831
|
+
before: -1,
|
|
832
|
+
same: 0,
|
|
833
|
+
after: 1,
|
|
834
|
+
different: 1
|
|
835
|
+
};
|
|
836
|
+
return model.document.getRootNames().flatMap((rootName) => results.filter((result) => result.marker.getStart().root.rootName === rootName).sort((a, b) => sortMapping[a.marker.getStart().compareWith(b.marker.getStart())]));
|
|
932
837
|
}
|
|
933
838
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
}
|
|
839
|
+
/**
|
|
840
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
841
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
842
|
+
*/
|
|
843
|
+
/**
|
|
844
|
+
* @module find-and-replace/replacecommandbase
|
|
845
|
+
*/
|
|
846
|
+
var FindReplaceCommandBase = class extends Command {
|
|
847
|
+
/**
|
|
848
|
+
* The find and replace state object used for command operations.
|
|
849
|
+
*/
|
|
850
|
+
_state;
|
|
851
|
+
/**
|
|
852
|
+
* Creates a new `ReplaceCommand` instance.
|
|
853
|
+
*
|
|
854
|
+
* @param editor Editor on which this command will be used.
|
|
855
|
+
* @param state An object to hold plugin state.
|
|
856
|
+
*/
|
|
857
|
+
constructor(editor, state) {
|
|
858
|
+
super(editor);
|
|
859
|
+
this.isEnabled = true;
|
|
860
|
+
this._state = state;
|
|
861
|
+
this._isEnabledBasedOnSelection = false;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Common logic for both `replace` commands.
|
|
865
|
+
* Replace a given find result by a string or a callback.
|
|
866
|
+
*
|
|
867
|
+
* @param result A single result from the find command.
|
|
868
|
+
*/
|
|
869
|
+
_replace(replacementText, result) {
|
|
870
|
+
const { model } = this.editor;
|
|
871
|
+
const range = result.marker.getRange();
|
|
872
|
+
if (!model.canEditAt(range)) return;
|
|
873
|
+
model.change((writer) => {
|
|
874
|
+
if (range.root.rootName === "$graveyard") {
|
|
875
|
+
this._state.results.remove(result);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
let textAttributes = {};
|
|
879
|
+
for (const item of range.getItems()) if (item.is("$text") || item.is("$textProxy")) {
|
|
880
|
+
textAttributes = item.getAttributes();
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
model.insertContent(writer.createText(replacementText, textAttributes), range);
|
|
884
|
+
if (this._state.results.has(result)) this._state.results.remove(result);
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
};
|
|
983
888
|
|
|
984
889
|
/**
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
// Let's revert the highlight offset to the previous value.
|
|
1009
|
-
if (this._state.results.length) {
|
|
1010
|
-
// Highlight offset operates on sorted array, so we need to sort the results first.
|
|
1011
|
-
// It's not guaranteed that items in state results are sorted, usually they are, but it's not guaranteed when
|
|
1012
|
-
// the result is removed from the paragraph with other highlighted results.
|
|
1013
|
-
const sortedResults = sortSearchResultsByMarkerPositions(this.editor.model, [
|
|
1014
|
-
...this._state.results
|
|
1015
|
-
]);
|
|
1016
|
-
// Just make sure that we don't overflow the results array, so use modulo.
|
|
1017
|
-
this._state.highlightedResult = sortedResults[oldHighlightOffset % sortedResults.length];
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
890
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
891
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
892
|
+
*/
|
|
893
|
+
/**
|
|
894
|
+
* The replace command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
895
|
+
*/
|
|
896
|
+
var ReplaceCommand = class extends FindReplaceCommandBase {
|
|
897
|
+
/**
|
|
898
|
+
* Replace a given find result by a string or a callback.
|
|
899
|
+
*
|
|
900
|
+
* @param result A single result from the find command.
|
|
901
|
+
*
|
|
902
|
+
* @fires execute
|
|
903
|
+
*/
|
|
904
|
+
execute(replacementText, result) {
|
|
905
|
+
const oldHighlightOffset = Math.max(this._state.highlightedOffset - 1, 0);
|
|
906
|
+
this._replace(replacementText, result);
|
|
907
|
+
if (this._state.results.length) {
|
|
908
|
+
const sortedResults = sortSearchResultsByMarkerPositions(this.editor.model, [...this._state.results]);
|
|
909
|
+
this._state.highlightedResult = sortedResults[oldHighlightOffset % sortedResults.length];
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
1021
913
|
|
|
1022
914
|
/**
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
915
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
916
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
917
|
+
*/
|
|
918
|
+
/**
|
|
919
|
+
* @module find-and-replace/replaceallcommand
|
|
920
|
+
*/
|
|
921
|
+
/**
|
|
922
|
+
* The replace all command. It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
923
|
+
*/
|
|
924
|
+
var ReplaceAllCommand = class extends FindReplaceCommandBase {
|
|
925
|
+
/**
|
|
926
|
+
* Replaces all the occurrences of `textToReplace` with a given `newText` string.
|
|
927
|
+
*
|
|
928
|
+
* ```ts
|
|
929
|
+
* replaceAllCommand.execute( 'replaceAll', 'new text replacement', 'text to replace' );
|
|
930
|
+
* ```
|
|
931
|
+
*
|
|
932
|
+
* Alternatively you can call it from editor instance:
|
|
933
|
+
*
|
|
934
|
+
* ```ts
|
|
935
|
+
* editor.execute( 'replaceAll', 'new text', 'old text' );
|
|
936
|
+
* ```
|
|
937
|
+
*
|
|
938
|
+
* @param newText Text that will be inserted to the editor for each match.
|
|
939
|
+
* @param textToReplace Text to be replaced or a collection of matches
|
|
940
|
+
* as returned by the find command.
|
|
941
|
+
*
|
|
942
|
+
* @fires module:core/command~Command#event:execute
|
|
943
|
+
*/
|
|
944
|
+
execute(newText, textToReplace) {
|
|
945
|
+
const { editor } = this;
|
|
946
|
+
const { model } = editor;
|
|
947
|
+
const findAndReplaceUtils = editor.plugins.get("FindAndReplaceUtils");
|
|
948
|
+
const results = textToReplace instanceof Collection ? textToReplace : model.document.getRootNames().reduce(((currentResults, rootName) => findAndReplaceUtils.updateFindResultFromRange(model.createRangeIn(model.document.getRoot(rootName)), model, findAndReplaceUtils.findByTextCallback(textToReplace, this._state), currentResults)), null);
|
|
949
|
+
if (results.length) model.change(() => {
|
|
950
|
+
[...results].forEach((searchResult) => {
|
|
951
|
+
this._replace(newText, searchResult);
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
};
|
|
1061
956
|
|
|
1062
957
|
/**
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
958
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
959
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
960
|
+
*/
|
|
961
|
+
/**
|
|
962
|
+
* @module find-and-replace/findnextcommand
|
|
963
|
+
*/
|
|
964
|
+
/**
|
|
965
|
+
* The find next command. Moves the highlight to the next search result.
|
|
966
|
+
*
|
|
967
|
+
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
968
|
+
*/
|
|
969
|
+
var FindNextCommand = class extends Command {
|
|
970
|
+
/**
|
|
971
|
+
* The find and replace state object used for command operations.
|
|
972
|
+
*/
|
|
973
|
+
_state;
|
|
974
|
+
/**
|
|
975
|
+
* Creates a new `FindNextCommand` instance.
|
|
976
|
+
*
|
|
977
|
+
* @param editor The editor on which this command will be used.
|
|
978
|
+
* @param state An object to hold plugin state.
|
|
979
|
+
*/
|
|
980
|
+
constructor(editor, state) {
|
|
981
|
+
super(editor);
|
|
982
|
+
this.affectsData = false;
|
|
983
|
+
this._state = state;
|
|
984
|
+
this.isEnabled = false;
|
|
985
|
+
this.listenTo(this._state.results, "change", () => {
|
|
986
|
+
this.isEnabled = this._state.results.length > 1;
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* @inheritDoc
|
|
991
|
+
*/
|
|
992
|
+
refresh() {
|
|
993
|
+
this.isEnabled = this._state.results.length > 1;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* @inheritDoc
|
|
997
|
+
*/
|
|
998
|
+
execute() {
|
|
999
|
+
const results = this._state.results;
|
|
1000
|
+
const currentIndex = results.getIndex(this._state.highlightedResult);
|
|
1001
|
+
const nextIndex = currentIndex + 1 >= results.length ? 0 : currentIndex + 1;
|
|
1002
|
+
this._state.highlightedResult = this._state.results.get(nextIndex);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1099
1005
|
|
|
1100
1006
|
/**
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1007
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1008
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1009
|
+
*/
|
|
1010
|
+
/**
|
|
1011
|
+
* @module find-and-replace/findpreviouscommand
|
|
1012
|
+
*/
|
|
1013
|
+
/**
|
|
1014
|
+
* The find previous command. Moves the highlight to the previous search result.
|
|
1015
|
+
*
|
|
1016
|
+
* It is used by the {@link module:find-and-replace/findandreplace~FindAndReplace find and replace feature}.
|
|
1017
|
+
*/
|
|
1018
|
+
var FindPreviousCommand = class extends FindNextCommand {
|
|
1019
|
+
/**
|
|
1020
|
+
* @inheritDoc
|
|
1021
|
+
*/
|
|
1022
|
+
execute() {
|
|
1023
|
+
const currentIndex = this._state.results.getIndex(this._state.highlightedResult);
|
|
1024
|
+
const previousIndex = currentIndex - 1 < 0 ? this._state.results.length - 1 : currentIndex - 1;
|
|
1025
|
+
this._state.highlightedResult = this._state.results.get(previousIndex);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1114
1028
|
|
|
1115
1029
|
/**
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
if (!new RegExp(nonLetterGroup + '$').test(searchTerm)) {
|
|
1231
|
-
regExpQuery = `${regExpQuery}(?=_|${nonLetterGroup}|$)`;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
const regExp = new RegExp(regExpQuery, flags);
|
|
1235
|
-
function findCallback({ text }) {
|
|
1236
|
-
const matches = [
|
|
1237
|
-
...text.matchAll(regExp)
|
|
1238
|
-
];
|
|
1239
|
-
return matches.map(regexpMatchToFindResult);
|
|
1240
|
-
}
|
|
1241
|
-
return findCallback;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
// Finds the appropriate index in the resultsList Collection.
|
|
1030
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1031
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1032
|
+
*/
|
|
1033
|
+
/**
|
|
1034
|
+
* A set of helpers related to find and replace.
|
|
1035
|
+
*/
|
|
1036
|
+
var FindAndReplaceUtils = class extends Plugin {
|
|
1037
|
+
/**
|
|
1038
|
+
* @inheritDoc
|
|
1039
|
+
*/
|
|
1040
|
+
static get pluginName() {
|
|
1041
|
+
return "FindAndReplaceUtils";
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* @inheritDoc
|
|
1045
|
+
*/
|
|
1046
|
+
static get isOfficialPlugin() {
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Executes findCallback and updates search results list.
|
|
1051
|
+
*
|
|
1052
|
+
* @param range The model range to scan for matches.
|
|
1053
|
+
* @param model The model.
|
|
1054
|
+
* @param findCallback The callback that should return `true` if provided text matches the search term.
|
|
1055
|
+
* @param startResults An optional collection of find matches that the function should
|
|
1056
|
+
* start with. This would be a collection returned by a previous `updateFindResultFromRange()` call.
|
|
1057
|
+
* @returns A collection of objects describing find match.
|
|
1058
|
+
*
|
|
1059
|
+
* An example structure:
|
|
1060
|
+
*
|
|
1061
|
+
* ```js
|
|
1062
|
+
* {
|
|
1063
|
+
* id: resultId,
|
|
1064
|
+
* label: foundItem.label,
|
|
1065
|
+
* marker
|
|
1066
|
+
* }
|
|
1067
|
+
* ```
|
|
1068
|
+
*/
|
|
1069
|
+
updateFindResultFromRange(range, model, findCallback, startResults) {
|
|
1070
|
+
const results = startResults || new Collection();
|
|
1071
|
+
const checkIfResultAlreadyOnList = (marker) => results.find((markerItem) => {
|
|
1072
|
+
const { marker: resultsMarker } = markerItem;
|
|
1073
|
+
const resultRange = resultsMarker.getRange();
|
|
1074
|
+
const markerRange = marker.getRange();
|
|
1075
|
+
return resultRange.isEqual(markerRange);
|
|
1076
|
+
});
|
|
1077
|
+
model.change((writer) => {
|
|
1078
|
+
[...range].forEach(({ type, item }) => {
|
|
1079
|
+
if (type === "elementStart") {
|
|
1080
|
+
if (model.schema.checkChild(item, "$text")) {
|
|
1081
|
+
let foundItems = findCallback({
|
|
1082
|
+
item,
|
|
1083
|
+
text: this.rangeToText(model.createRangeIn(item))
|
|
1084
|
+
});
|
|
1085
|
+
if (!foundItems) return;
|
|
1086
|
+
if ("results" in foundItems) foundItems = foundItems.results;
|
|
1087
|
+
foundItems.forEach((foundItem) => {
|
|
1088
|
+
const resultId = `findResult:${uid()}`;
|
|
1089
|
+
const marker = writer.addMarker(resultId, {
|
|
1090
|
+
usingOperation: false,
|
|
1091
|
+
affectsData: false,
|
|
1092
|
+
range: writer.createRange(writer.createPositionAt(item, foundItem.start), writer.createPositionAt(item, foundItem.end))
|
|
1093
|
+
});
|
|
1094
|
+
const index = findInsertIndex(results, marker);
|
|
1095
|
+
if (!checkIfResultAlreadyOnList(marker)) results.add({
|
|
1096
|
+
id: resultId,
|
|
1097
|
+
label: foundItem.label,
|
|
1098
|
+
marker
|
|
1099
|
+
}, index);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
return results;
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Returns text representation of a range. The returned text length should be the same as range length.
|
|
1109
|
+
* In order to achieve this, this function will replace inline elements (text-line) as new line character ("\n").
|
|
1110
|
+
*
|
|
1111
|
+
* @param range The model range.
|
|
1112
|
+
* @returns The text content of the provided range.
|
|
1113
|
+
*/
|
|
1114
|
+
rangeToText(range) {
|
|
1115
|
+
return Array.from(range.getItems({ shallow: true })).reduce((rangeText, node) => {
|
|
1116
|
+
if (!(node.is("$text") || node.is("$textProxy"))) return `${rangeText}\n`;
|
|
1117
|
+
return rangeText + node.data;
|
|
1118
|
+
}, "");
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Creates a text matching callback for a specified search term and matching options.
|
|
1122
|
+
*
|
|
1123
|
+
* @param searchTerm The search term.
|
|
1124
|
+
* @param options Matching options.
|
|
1125
|
+
* - options.matchCase=false If set to `true` letter casing will be ignored.
|
|
1126
|
+
* - options.wholeWords=false If set to `true` only whole words that match `callbackOrText` will be matched.
|
|
1127
|
+
*/
|
|
1128
|
+
findByTextCallback(searchTerm, options) {
|
|
1129
|
+
let flags = "gu";
|
|
1130
|
+
if (!options.matchCase) flags += "i";
|
|
1131
|
+
let regExpQuery = `(${escapeRegExp(searchTerm)})`;
|
|
1132
|
+
if (options.wholeWords) {
|
|
1133
|
+
const nonLetterGroup = "[^a-zA-ZÀ-ɏḀ-ỿ]";
|
|
1134
|
+
if (!(/* @__PURE__ */ new RegExp("^[^a-zA-ZÀ-ɏḀ-ỿ]")).test(searchTerm)) regExpQuery = `(^|${nonLetterGroup}|_)${regExpQuery}`;
|
|
1135
|
+
if (!(/* @__PURE__ */ new RegExp("[^a-zA-ZÀ-ɏḀ-ỿ]$")).test(searchTerm)) regExpQuery = `${regExpQuery}(?=_|${nonLetterGroup}|$)`;
|
|
1136
|
+
}
|
|
1137
|
+
const regExp = new RegExp(regExpQuery, flags);
|
|
1138
|
+
function findCallback({ text }) {
|
|
1139
|
+
return [...text.matchAll(regExp)].map(regexpMatchToFindResult);
|
|
1140
|
+
}
|
|
1141
|
+
return findCallback;
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1245
1144
|
function findInsertIndex(resultsList, markerToInsert) {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1145
|
+
const result = resultsList.find(({ marker }) => {
|
|
1146
|
+
return markerToInsert.getStart().isBefore(marker.getStart());
|
|
1147
|
+
});
|
|
1148
|
+
return result ? resultsList.getIndex(result) : resultsList.length;
|
|
1250
1149
|
}
|
|
1251
1150
|
/**
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
start: startOffset,
|
|
1264
|
-
end: startOffset + matchResult[lastGroupIndex].length
|
|
1265
|
-
};
|
|
1151
|
+
* Maps RegExp match result to find result.
|
|
1152
|
+
*/
|
|
1153
|
+
function regexpMatchToFindResult(matchResult) {
|
|
1154
|
+
const lastGroupIndex = matchResult.length - 1;
|
|
1155
|
+
let startOffset = matchResult.index;
|
|
1156
|
+
if (matchResult.length === 3) startOffset += matchResult[1].length;
|
|
1157
|
+
return {
|
|
1158
|
+
label: matchResult[lastGroupIndex],
|
|
1159
|
+
start: startOffset,
|
|
1160
|
+
end: startOffset + matchResult[lastGroupIndex].length
|
|
1161
|
+
};
|
|
1266
1162
|
}
|
|
1267
1163
|
|
|
1268
|
-
const HIGHLIGHT_CLASS = 'ck-find-result_selected';
|
|
1269
1164
|
/**
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
if (!results.has(markerName)) {
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
if (results.get(markerName) === this.state.highlightedResult) {
|
|
1464
|
-
this.state.highlightedResult = null;
|
|
1465
|
-
}
|
|
1466
|
-
results.remove(markerName);
|
|
1467
|
-
});
|
|
1468
|
-
// Run search callback again on updated nodes.
|
|
1469
|
-
const changedSearchResults = [];
|
|
1470
|
-
const findAndReplaceUtils = this.editor.plugins.get('FindAndReplaceUtils');
|
|
1471
|
-
changedNodes.forEach((nodeToCheck)=>{
|
|
1472
|
-
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
|
|
1473
|
-
changedSearchResults.push(...changedNodeSearchResults);
|
|
1474
|
-
});
|
|
1475
|
-
changedMarkers.forEach((markerToCheck)=>{
|
|
1476
|
-
// Handle search result highlight update when T&C plugin is active.
|
|
1477
|
-
// Lookup is performed only on newly inserted markers.
|
|
1478
|
-
if (markerToCheck.data.newRange) {
|
|
1479
|
-
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
|
|
1480
|
-
changedSearchResults.push(...changedNodeSearchResults);
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
if (!this.state.highlightedResult && changedSearchResults.length) {
|
|
1484
|
-
// If there are found phrases but none is selected, select the first one.
|
|
1485
|
-
this.state.highlightedResult = changedSearchResults[0];
|
|
1486
|
-
} else {
|
|
1487
|
-
// If there is already highlight item then refresh highlight offset after appending new items.
|
|
1488
|
-
this.state.refreshHighlightOffset(model);
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1165
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1166
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1167
|
+
*/
|
|
1168
|
+
/**
|
|
1169
|
+
* @module find-and-replace/findandreplaceediting
|
|
1170
|
+
*/
|
|
1171
|
+
const HIGHLIGHT_CLASS = "ck-find-result_selected";
|
|
1172
|
+
/**
|
|
1173
|
+
* Implements the editing part for find and replace plugin. For example conversion, commands etc.
|
|
1174
|
+
*/
|
|
1175
|
+
var FindAndReplaceEditing = class extends Plugin {
|
|
1176
|
+
/**
|
|
1177
|
+
* @inheritDoc
|
|
1178
|
+
*/
|
|
1179
|
+
static get requires() {
|
|
1180
|
+
return [FindAndReplaceUtils];
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* @inheritDoc
|
|
1184
|
+
*/
|
|
1185
|
+
static get pluginName() {
|
|
1186
|
+
return "FindAndReplaceEditing";
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* @inheritDoc
|
|
1190
|
+
* @internal
|
|
1191
|
+
*/
|
|
1192
|
+
static get licenseFeatureCode() {
|
|
1193
|
+
return "FAR";
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* @inheritDoc
|
|
1197
|
+
*/
|
|
1198
|
+
static get isPremiumPlugin() {
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* @inheritDoc
|
|
1203
|
+
*/
|
|
1204
|
+
static get isOfficialPlugin() {
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* An object storing the find and replace state within a given editor instance.
|
|
1209
|
+
*/
|
|
1210
|
+
state;
|
|
1211
|
+
/**
|
|
1212
|
+
* @inheritDoc
|
|
1213
|
+
*/
|
|
1214
|
+
init() {
|
|
1215
|
+
this.state = new FindAndReplaceState(this.editor.model);
|
|
1216
|
+
this.set("_isSearchActive", false);
|
|
1217
|
+
this._defineConverters();
|
|
1218
|
+
this._defineCommands();
|
|
1219
|
+
this.listenTo(this.state, "change:highlightedResult", (eventInfo, name, newValue, oldValue) => {
|
|
1220
|
+
const { model } = this.editor;
|
|
1221
|
+
model.change((writer) => {
|
|
1222
|
+
if (oldValue) {
|
|
1223
|
+
const oldMatchId = oldValue.marker.name.split(":")[1];
|
|
1224
|
+
const oldMarker = model.markers.get(`findResultHighlighted:${oldMatchId}`);
|
|
1225
|
+
if (oldMarker) writer.removeMarker(oldMarker);
|
|
1226
|
+
}
|
|
1227
|
+
if (newValue) {
|
|
1228
|
+
const newMatchId = newValue.marker.name.split(":")[1];
|
|
1229
|
+
writer.addMarker(`findResultHighlighted:${newMatchId}`, {
|
|
1230
|
+
usingOperation: false,
|
|
1231
|
+
affectsData: false,
|
|
1232
|
+
range: newValue.marker.getRange()
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
/* v8 ignore next -- @preserve */
|
|
1238
|
+
const scrollToHighlightedResult = (eventInfo, name, newValue) => {
|
|
1239
|
+
if (newValue) {
|
|
1240
|
+
const domConverter = this.editor.editing.view.domConverter;
|
|
1241
|
+
const viewRange = this.editor.editing.mapper.toViewRange(newValue.marker.getRange());
|
|
1242
|
+
scrollViewportToShowTarget({
|
|
1243
|
+
target: domConverter.viewRangeToDom(viewRange),
|
|
1244
|
+
viewportOffset: 40
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
const debouncedScrollListener = debounce(scrollToHighlightedResult.bind(this), 32);
|
|
1249
|
+
this.listenTo(this.state, "change:highlightedResult", debouncedScrollListener, { priority: "low" });
|
|
1250
|
+
this.listenTo(this.editor, "destroy", debouncedScrollListener.cancel);
|
|
1251
|
+
this.on("change:_isSearchActive", (evt, name, isSearchActive) => {
|
|
1252
|
+
if (isSearchActive) this.listenTo(this.editor.model.document, "change:data", this._onDocumentChange);
|
|
1253
|
+
else this.stopListening(this.editor.model.document, "change:data", this._onDocumentChange);
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Initiate a search.
|
|
1258
|
+
*/
|
|
1259
|
+
find(callbackOrText, findAttributes) {
|
|
1260
|
+
this._isSearchActive = true;
|
|
1261
|
+
this.editor.execute("find", callbackOrText, findAttributes);
|
|
1262
|
+
return this.state.results;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Stops active results from updating, and clears out the results.
|
|
1266
|
+
*/
|
|
1267
|
+
stop() {
|
|
1268
|
+
this.state.clear(this.editor.model);
|
|
1269
|
+
this._isSearchActive = false;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Sets up the commands.
|
|
1273
|
+
*/
|
|
1274
|
+
_defineCommands() {
|
|
1275
|
+
this.editor.commands.add("find", new FindCommand(this.editor, this.state));
|
|
1276
|
+
this.editor.commands.add("findNext", new FindNextCommand(this.editor, this.state));
|
|
1277
|
+
this.editor.commands.add("findPrevious", new FindPreviousCommand(this.editor, this.state));
|
|
1278
|
+
this.editor.commands.add("replace", new ReplaceCommand(this.editor, this.state));
|
|
1279
|
+
this.editor.commands.add("replaceAll", new ReplaceAllCommand(this.editor, this.state));
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Sets up the marker downcast converters for search results highlighting.
|
|
1283
|
+
*/
|
|
1284
|
+
_defineConverters() {
|
|
1285
|
+
const { editor } = this;
|
|
1286
|
+
editor.conversion.for("editingDowncast").markerToHighlight({
|
|
1287
|
+
model: "findResult",
|
|
1288
|
+
view: ({ markerName }) => {
|
|
1289
|
+
const [, id] = markerName.split(":");
|
|
1290
|
+
return {
|
|
1291
|
+
name: "span",
|
|
1292
|
+
classes: ["ck-find-result"],
|
|
1293
|
+
attributes: { "data-find-result": id }
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
editor.conversion.for("editingDowncast").markerToHighlight({
|
|
1298
|
+
model: "findResultHighlighted",
|
|
1299
|
+
view: ({ markerName }) => {
|
|
1300
|
+
const [, id] = markerName.split(":");
|
|
1301
|
+
return {
|
|
1302
|
+
name: "span",
|
|
1303
|
+
classes: [HIGHLIGHT_CLASS],
|
|
1304
|
+
attributes: { "data-find-result": id }
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Reacts to document changes in order to update search list.
|
|
1311
|
+
*/
|
|
1312
|
+
_onDocumentChange = () => {
|
|
1313
|
+
const changedNodes = /* @__PURE__ */ new Set();
|
|
1314
|
+
const removedMarkers = /* @__PURE__ */ new Set();
|
|
1315
|
+
const model = this.editor.model;
|
|
1316
|
+
const { results } = this.state;
|
|
1317
|
+
const changes = model.document.differ.getChanges();
|
|
1318
|
+
const changedMarkers = model.document.differ.getChangedMarkers();
|
|
1319
|
+
changes.forEach((change) => {
|
|
1320
|
+
if (!change.position) return;
|
|
1321
|
+
if (change.name === "$text" || change.position.nodeAfter && model.schema.isInline(change.position.nodeAfter)) {
|
|
1322
|
+
changedNodes.add(change.position.parent);
|
|
1323
|
+
[...model.markers.getMarkersAtPosition(change.position)].forEach((markerAtChange) => {
|
|
1324
|
+
removedMarkers.add(markerAtChange.name);
|
|
1325
|
+
});
|
|
1326
|
+
} else if (change.type === "insert" && change.position.nodeAfter) changedNodes.add(change.position.nodeAfter);
|
|
1327
|
+
});
|
|
1328
|
+
changedMarkers.forEach(({ name, data: { newRange } }) => {
|
|
1329
|
+
if (newRange && newRange.start.root.rootName === "$graveyard") removedMarkers.add(name);
|
|
1330
|
+
});
|
|
1331
|
+
changedNodes.forEach((node) => {
|
|
1332
|
+
[...model.markers.getMarkersIntersectingRange(model.createRangeIn(node))].forEach((marker) => removedMarkers.add(marker.name));
|
|
1333
|
+
});
|
|
1334
|
+
removedMarkers.forEach((markerName) => {
|
|
1335
|
+
if (!results.has(markerName)) return;
|
|
1336
|
+
if (results.get(markerName) === this.state.highlightedResult) this.state.highlightedResult = null;
|
|
1337
|
+
results.remove(markerName);
|
|
1338
|
+
});
|
|
1339
|
+
const changedSearchResults = [];
|
|
1340
|
+
const findAndReplaceUtils = this.editor.plugins.get("FindAndReplaceUtils");
|
|
1341
|
+
changedNodes.forEach((nodeToCheck) => {
|
|
1342
|
+
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(model.createRangeOn(nodeToCheck), model, this.state.lastSearchCallback, results);
|
|
1343
|
+
changedSearchResults.push(...changedNodeSearchResults);
|
|
1344
|
+
});
|
|
1345
|
+
changedMarkers.forEach((markerToCheck) => {
|
|
1346
|
+
if (markerToCheck.data.newRange) {
|
|
1347
|
+
const changedNodeSearchResults = findAndReplaceUtils.updateFindResultFromRange(markerToCheck.data.newRange, model, this.state.lastSearchCallback, results);
|
|
1348
|
+
changedSearchResults.push(...changedNodeSearchResults);
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
if (!this.state.highlightedResult && changedSearchResults.length) this.state.highlightedResult = changedSearchResults[0];
|
|
1352
|
+
else this.state.refreshHighlightOffset(model);
|
|
1353
|
+
};
|
|
1354
|
+
};
|
|
1492
1355
|
|
|
1493
1356
|
/**
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
});
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1357
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1358
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1359
|
+
*/
|
|
1360
|
+
/**
|
|
1361
|
+
* @module find-and-replace/findandreplace
|
|
1362
|
+
*/
|
|
1363
|
+
/**
|
|
1364
|
+
* The find and replace plugin.
|
|
1365
|
+
*
|
|
1366
|
+
* For a detailed overview, check the {@glink features/find-and-replace Find and replace feature documentation}.
|
|
1367
|
+
*
|
|
1368
|
+
* This is a "glue" plugin which loads the following plugins:
|
|
1369
|
+
*
|
|
1370
|
+
* * The {@link module:find-and-replace/findandreplaceediting~FindAndReplaceEditing find and replace editing feature},
|
|
1371
|
+
* * The {@link module:find-and-replace/findandreplaceui~FindAndReplaceUI find and replace UI feature}
|
|
1372
|
+
*/
|
|
1373
|
+
var FindAndReplace = class extends Plugin {
|
|
1374
|
+
/**
|
|
1375
|
+
* @inheritDoc
|
|
1376
|
+
*/
|
|
1377
|
+
static get requires() {
|
|
1378
|
+
return [FindAndReplaceEditing, FindAndReplaceUI];
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* @inheritDoc
|
|
1382
|
+
*/
|
|
1383
|
+
static get pluginName() {
|
|
1384
|
+
return "FindAndReplace";
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* @inheritDoc
|
|
1388
|
+
*/
|
|
1389
|
+
static get isOfficialPlugin() {
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* @inheritDoc
|
|
1394
|
+
*/
|
|
1395
|
+
init() {
|
|
1396
|
+
const ui = this.editor.plugins.get("FindAndReplaceUI");
|
|
1397
|
+
const findAndReplaceEditing = this.editor.plugins.get("FindAndReplaceEditing");
|
|
1398
|
+
const state = findAndReplaceEditing.state;
|
|
1399
|
+
ui.on("findNext", (event, data) => {
|
|
1400
|
+
if (data) {
|
|
1401
|
+
state.searchText = data.searchText;
|
|
1402
|
+
findAndReplaceEditing.find(data.searchText, data);
|
|
1403
|
+
} else this.editor.execute("findNext");
|
|
1404
|
+
});
|
|
1405
|
+
ui.on("findPrevious", (event, data) => {
|
|
1406
|
+
if (data && state.searchText !== data.searchText) findAndReplaceEditing.find(data.searchText);
|
|
1407
|
+
else this.editor.execute("findPrevious");
|
|
1408
|
+
});
|
|
1409
|
+
ui.on("replace", (event, data) => {
|
|
1410
|
+
if (state.searchText !== data.searchText) findAndReplaceEditing.find(data.searchText);
|
|
1411
|
+
const highlightedResult = state.highlightedResult;
|
|
1412
|
+
if (highlightedResult) this.editor.execute("replace", data.replaceText, highlightedResult);
|
|
1413
|
+
});
|
|
1414
|
+
ui.on("replaceAll", (event, data) => {
|
|
1415
|
+
if (state.searchText !== data.searchText) findAndReplaceEditing.find(data.searchText);
|
|
1416
|
+
this.editor.execute("replaceAll", data.replaceText, state.results);
|
|
1417
|
+
});
|
|
1418
|
+
ui.on("searchReseted", () => {
|
|
1419
|
+
state.clear(this.editor.model);
|
|
1420
|
+
findAndReplaceEditing.stop();
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
|
|
1427
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
1428
|
+
*/
|
|
1569
1429
|
|
|
1570
1430
|
export { FindAndReplace, FindAndReplaceEditing, FindAndReplaceFormView, FindAndReplaceState, FindAndReplaceUI, FindAndReplaceUtils, FindCommand, FindNextCommand, FindPreviousCommand, FindReplaceCommandBase, ReplaceAllCommand, ReplaceCommand, sortSearchResultsByMarkerPositions as _sortFindResultsByMarkerPositions };
|
|
1571
|
-
//# sourceMappingURL=index.js.map
|
|
1431
|
+
//# sourceMappingURL=index.js.map
|