@webcoder49/code-input 2.2.1 → 2.5.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.
@@ -6,15 +6,40 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
6
6
  useCtrlF = false;
7
7
  useCtrlH = false;
8
8
 
9
+ findMatchesOnValueChange = true; // Needed so the program can insert text to the find value and thus add it to Ctrl+Z without highlighting matches.
10
+
11
+ instructions = {
12
+ start: "Search for matches in your code.",
13
+ none: "No matches",
14
+ oneFound: "1 match found.",
15
+ matchIndex: (index, count) => `${index} of ${count} matches.`,
16
+ error: (message) => `Error: ${message}`,
17
+ infiniteLoopError: "Causes an infinite loop",
18
+ closeDialog: "Close Dialog and Return to Editor",
19
+ findPlaceholder: "Find",
20
+ findCaseSensitive: "Match Case Sensitive",
21
+ findRegExp: "Use JavaScript Regular Expression",
22
+ replaceTitle: "Replace",
23
+ replacePlaceholder: "Replace with",
24
+ findNext: "Find Next Occurrence",
25
+ findPrevious: "Find Previous Occurrence",
26
+ replaceActionShort: "Replace",
27
+ replaceAction: "Replace This Occurrence",
28
+ replaceAllActionShort: "Replace All",
29
+ replaceAllAction: "Replace All Occurrences"
30
+ };
31
+
9
32
  /**
10
33
  * Create a find-and-replace command plugin to pass into a template
11
34
  * @param {boolean} useCtrlF Should Ctrl+F be overriden for find-and-replace find functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element, false)`.
12
35
  * @param {boolean} useCtrlH Should Ctrl+H be overriden for find-and-replace replace functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element, true)`.
36
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the find-and-replace.js source code for the English text and available keys.
13
37
  */
14
- constructor(useCtrlF = true, useCtrlH = true) {
38
+ constructor(useCtrlF = true, useCtrlH = true, instructionTranslations = {}) {
15
39
  super([]); // No observed attributes
16
40
  this.useCtrlF = useCtrlF;
17
41
  this.useCtrlH = useCtrlH;
42
+ this.addTranslations(this.instructions, instructionTranslations);
18
43
  }
19
44
 
20
45
  /* Add keystroke events */
@@ -54,13 +79,13 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
54
79
  updateMatchDescription(dialog) {
55
80
  // 1-indexed
56
81
  if(dialog.findInput.value.length == 0) {
57
- dialog.matchDescription.textContent = "Search for matches in your code.";
82
+ dialog.matchDescription.textContent = this.instructions.start;
58
83
  } else if(dialog.findMatchState.numMatches <= 0) {
59
- dialog.matchDescription.textContent = "No matches.";
84
+ dialog.matchDescription.textContent = this.instructions.none;
60
85
  } else if(dialog.findMatchState.numMatches == 1) {
61
- dialog.matchDescription.textContent = "1 match found.";
86
+ dialog.matchDescription.textContent = this.instructions.oneFound;
62
87
  } else {
63
- dialog.matchDescription.textContent = `${dialog.findMatchState.focusedMatchID+1} of ${dialog.findMatchState.numMatches} matches.`;
88
+ dialog.matchDescription.textContent = this.instructions.matchIndex(dialog.findMatchState.focusedMatchID+1, dialog.findMatchState.numMatches);
64
89
  }
65
90
  }
66
91
 
@@ -81,7 +106,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
81
106
  dialog.findInput.classList.add('code-input_find-and-replace_error');
82
107
  // Only show last part of error message
83
108
  let messageParts = err.message.split(": ");
84
- dialog.matchDescription.textContent = "Error: " + messageParts[messageParts.length-1]; // Show only last part of error.
109
+ dialog.matchDescription.textContent = this.instructions.error(messageParts[messageParts.length-1]); // Show only last part of error.
85
110
  return;
86
111
  } else {
87
112
  throw err;
@@ -100,7 +125,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
100
125
  }, 100);
101
126
  }
102
127
 
103
- /* Deal with Enter and Escape being pressed in the find field */
128
+ /* Deal with Enter being pressed in the find field */
104
129
  checkFindPrompt(dialog, codeInput, event) {
105
130
  if (event.key == 'Enter') {
106
131
  // Find next match
@@ -109,11 +134,12 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
109
134
  }
110
135
  }
111
136
 
112
- /* Deal with Enter and Escape being pressed in the replace field */
137
+ /* Deal with Enter being pressed in the replace field */
113
138
  checkReplacePrompt(dialog, codeInput, event) {
114
139
  if (event.key == 'Enter') {
115
140
  // Replace focused match
116
141
  dialog.findMatchState.replaceOnce(dialog.replaceInput.value);
142
+ dialog.replaceInput.focus();
117
143
  this.updateMatchDescription(dialog);
118
144
  }
119
145
  }
@@ -121,9 +147,21 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
121
147
  /* Called with a dialog box keyup event to close and clear the dialog box */
122
148
  cancelPrompt(dialog, codeInput, event) {
123
149
  event.preventDefault();
124
-
150
+
151
+ // Add current value of find/replace to Ctrl+Z stack.
152
+ this.findMatchesOnValueChange = false;
153
+ dialog.findInput.focus();
154
+ dialog.findInput.selectionStart = 0;
155
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
156
+ document.execCommand("insertText", false, dialog.findInput.value);
157
+ this.findMatchesOnValueChange = true;
158
+
125
159
  // Reset original selection in code-input
126
160
  dialog.textarea.focus();
161
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
162
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
163
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
164
+
127
165
  if(dialog.findMatchState.numMatches > 0) {
128
166
  // Select focused match
129
167
  codeInput.textareaElement.selectionStart = dialog.findMatchState.matchStartIndexes[dialog.findMatchState.focusedMatchID];
@@ -146,14 +184,16 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
146
184
  * @param {boolean} replacePartExpanded whether the replace part of the find-and-replace dialog should be expanded
147
185
  */
148
186
  showPrompt(codeInputElement, replacePartExpanded) {
187
+ let dialog;
149
188
  if(codeInputElement.pluginData.findAndReplace == undefined || codeInputElement.pluginData.findAndReplace.dialog == undefined) {
150
189
  const textarea = codeInputElement.textareaElement;
151
190
 
152
- const dialog = document.createElement('div');
191
+ dialog = document.createElement('div');
153
192
  const findInput = document.createElement('input');
154
193
  const findCaseSensitiveCheckbox = document.createElement('input');
155
194
  const findRegExpCheckbox = document.createElement('input');
156
195
  const matchDescription = document.createElement('code');
196
+ matchDescription.setAttribute("aria-live", "assertive"); // Screen reader must read the number of matches found.
157
197
 
158
198
  const replaceInput = document.createElement('input');
159
199
  const replaceDropdown = document.createElement('details');
@@ -165,6 +205,8 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
165
205
  const replaceButton = document.createElement('button');
166
206
  const replaceAllButton = document.createElement('button');
167
207
  const cancel = document.createElement('span');
208
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
209
+ cancel.setAttribute("title", this.instructions.closeDialog);
168
210
 
169
211
  buttonContainer.appendChild(findNextButton);
170
212
  buttonContainer.appendChild(findPreviousButton);
@@ -184,31 +226,39 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
184
226
 
185
227
  dialog.className = 'code-input_find-and-replace_dialog';
186
228
  findInput.spellcheck = false;
187
- findInput.placeholder = "Find";
229
+ findInput.placeholder = this.instructions.findPlaceholder;
188
230
  findCaseSensitiveCheckbox.setAttribute("type", "checkbox");
189
- findCaseSensitiveCheckbox.title = "Match Case Sensitive";
231
+ findCaseSensitiveCheckbox.title = this.instructions.findCaseSensitive;
190
232
  findCaseSensitiveCheckbox.classList.add("code-input_find-and-replace_case-sensitive-checkbox");
191
233
  findRegExpCheckbox.setAttribute("type", "checkbox");
192
- findRegExpCheckbox.title = "Use JavaScript Regular Expression";
234
+ findRegExpCheckbox.title = this.instructions.findRegExp;
193
235
  findRegExpCheckbox.classList.add("code-input_find-and-replace_reg-exp-checkbox");
194
236
 
195
237
  matchDescription.textContent = "Search for matches in your code.";
196
238
  matchDescription.classList.add("code-input_find-and-replace_match-description");
197
239
 
198
240
 
199
- replaceSummary.innerText = "Replace";
241
+ replaceSummary.innerText = this.instructions.replaceTitle;
200
242
  replaceInput.spellcheck = false;
201
- replaceInput.placeholder = "Replace with";
243
+ replaceInput.placeholder = this.instructions.replacePlaceholder;
202
244
  findNextButton.innerText = "↓";
203
- findNextButton.title = "Find Next Occurence";
245
+ findNextButton.title = this.instructions.findNext;
204
246
  findPreviousButton.innerText = "↑";
205
- findPreviousButton.title = "Find Previous Occurence";
247
+ findPreviousButton.title = this.instructions.findPrevious;
206
248
  replaceButton.className = 'code-input_find-and-replace_button-hidden';
207
- replaceButton.innerText = "Replace";
208
- replaceButton.title = "Replace This Occurence";
249
+ replaceButton.innerText = this.instructions.replaceActionShort;
250
+ replaceButton.title = this.instructions.replaceAction;
251
+ replaceButton.addEventListener("focus", () => {
252
+ // Show replace section
253
+ replaceDropdown.setAttribute("open", true);
254
+ });
209
255
  replaceAllButton.className = 'code-input_find-and-replace_button-hidden';
210
- replaceAllButton.innerText = "Replace All";
211
- replaceAllButton.title = "Replace All Occurences";
256
+ replaceAllButton.innerText = this.instructions.replaceAllActionShort;
257
+ replaceAllButton.title = this.instructions.replaceAllAction;
258
+ replaceAllButton.addEventListener("focus", () => {
259
+ // Show replace section
260
+ replaceDropdown.setAttribute("open", true);
261
+ });
212
262
 
213
263
  findNextButton.addEventListener("click", (event) => {
214
264
  // Stop form submit
@@ -229,7 +279,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
229
279
  event.preventDefault();
230
280
 
231
281
  dialog.findMatchState.replaceOnce(replaceInput.value);
232
- replaceButton.focus();
282
+ dialog.focus();
233
283
  });
234
284
  replaceAllButton.addEventListener("click", (event) => {
235
285
  // Stop form submit
@@ -275,6 +325,16 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
275
325
  /* Stop enter from submitting form */
276
326
  if (event.key == 'Enter') event.preventDefault();
277
327
  });
328
+ replaceInput.addEventListener('input', (event) => {
329
+ // Ctrl+Z can trigger this. If the dialog/replace dropdown aren't open, open them!
330
+ if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
331
+ // Show prompt
332
+ this.showPrompt(dialog.codeInput, true);
333
+ } else if(!dialog.replaceDropdown.hasAttribute("open")) {
334
+ // Open dropdown
335
+ dialog.replaceDropdown.setAttribute("open", true);
336
+ }
337
+ });
278
338
 
279
339
  dialog.addEventListener('keyup', (event) => {
280
340
  /* Close prompt on Enter pressed */
@@ -282,7 +342,13 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
282
342
  });
283
343
 
284
344
  findInput.addEventListener('keyup', (event) => { this.checkFindPrompt(dialog, codeInputElement, event); });
285
- findInput.addEventListener('input', (event) => { this.updateFindMatches(dialog); });
345
+ findInput.addEventListener('input', (event) => {
346
+ if(this.findMatchesOnValueChange) this.updateFindMatches(dialog);
347
+ // Ctrl+Z can trigger this. If the dialog isn't open, open it!
348
+ if(dialog.classList.contains("code-input_find-and-replace_hidden-dialog")) {
349
+ this.showPrompt(dialog.codeInput, false);
350
+ }
351
+ });
286
352
  findCaseSensitiveCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
287
353
  findRegExpCheckbox.addEventListener('click', (event) => { this.updateFindMatches(dialog); });
288
354
 
@@ -291,6 +357,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
291
357
  replaceInput.focus();
292
358
  });
293
359
  cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, codeInputElement, event); });
360
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, codeInputElement, event); });
294
361
 
295
362
  codeInputElement.dialogContainerElement.appendChild(dialog);
296
363
  codeInputElement.pluginData.findAndReplace = {dialog: dialog};
@@ -303,24 +370,45 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
303
370
  // Save selection position
304
371
  dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
305
372
  dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
373
+
374
+ if(dialog.selectionStart < dialog.selectionEnd) {
375
+ // Copy selected text to Find input
376
+ let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
377
+ dialog.findInput.focus();
378
+ dialog.findInput.selectionStart = 0;
379
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
380
+ document.execCommand("insertText", false, textToFind);
381
+ }
306
382
  } else {
383
+ dialog = codeInputElement.pluginData.findAndReplace.dialog;
307
384
  // Re-open dialog
308
- codeInputElement.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
309
- codeInputElement.pluginData.findAndReplace.dialog.findInput.focus();
385
+ dialog.classList.remove("code-input_find-and-replace_hidden-dialog");
386
+ dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
387
+ dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
388
+ dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
389
+ dialog.findInput.focus();
310
390
  if(replacePartExpanded) {
311
- codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open", true);
391
+ dialog.replaceDropdown.setAttribute("open", true);
312
392
  } else {
313
- codeInputElement.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open");
393
+ dialog.replaceDropdown.removeAttribute("open");
314
394
  }
395
+ }
315
396
 
316
-
317
- // Highlight matches
318
- this.updateFindMatches(codeInputElement.pluginData.findAndReplace.dialog);
319
-
320
- // Save selection position
321
- codeInputElement.pluginData.findAndReplace.dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
322
- codeInputElement.pluginData.findAndReplace.dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
397
+ // Save selection position
398
+ dialog.selectionStart = codeInputElement.textareaElement.selectionStart;
399
+ dialog.selectionEnd = codeInputElement.textareaElement.selectionEnd;
400
+
401
+ if(dialog.selectionStart < dialog.selectionEnd) {
402
+ // Copy selected text to Find input
403
+ let textToFind = codeInputElement.textareaElement.value.substring(dialog.selectionStart, dialog.selectionEnd);
404
+ dialog.findInput.focus();
405
+ dialog.findInput.selectionStart = 0;
406
+ dialog.findInput.selectionEnd = dialog.findInput.value.length;
407
+ document.execCommand("insertText", false, textToFind);
323
408
  }
409
+
410
+ // Highlight matches
411
+ this.updateFindMatches(dialog);
324
412
  }
325
413
 
326
414
  /* Event handler for keydown event that makes Ctrl+F open find dialog */
@@ -405,7 +493,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
405
493
  while ((match = searchRegexp.exec(this.codeInput.value)) !== null) {
406
494
  let matchText = match[0];
407
495
  if(matchText.length == 0) {
408
- throw SyntaxError("Causes an infinite loop");
496
+ throw SyntaxError(this.instructions.infiniteLoopError);
409
497
  }
410
498
 
411
499
  // Add next match block if needed
@@ -441,12 +529,14 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
441
529
  this.focusedMatchStartIndex += replacementText.length;
442
530
 
443
531
  // Select the match
532
+ this.codeInput.handleEventsFromTextarea = false;
444
533
  this.codeInput.textareaElement.focus();
445
534
  this.codeInput.textareaElement.selectionStart = this.matchStartIndexes[this.focusedMatchID];
446
535
  this.codeInput.textareaElement.selectionEnd = this.matchEndIndexes[this.focusedMatchID];
447
536
 
448
537
  // Replace it with the replacement text
449
538
  document.execCommand("insertText", false, replacementText);
539
+ this.codeInput.handleEventsFromTextarea = true;
450
540
  }
451
541
  }
452
542
 
@@ -459,6 +549,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
459
549
  // Replace each match
460
550
 
461
551
  // Select the match, taking into account characters added before
552
+ this.codeInput.handleEventsFromTextarea = false;
462
553
  this.codeInput.textareaElement.focus();
463
554
  this.codeInput.textareaElement.selectionStart = this.matchStartIndexes[i] + numCharsAdded;
464
555
  this.codeInput.textareaElement.selectionEnd = this.matchEndIndexes[i] + numCharsAdded;
@@ -467,6 +558,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
467
558
 
468
559
  // Replace it with the replacement text
469
560
  document.execCommand("insertText", false, replacementText);
561
+ this.codeInput.handleEventsFromTextarea = true;
470
562
  }
471
563
  }
472
564
 
@@ -527,7 +619,8 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
527
619
 
528
620
  /* Highlight a match from the find functionality given its start and end indexes in the text.
529
621
  Start from the currentElement as this function is recursive. Use the matchID in the class name
530
- of the match so different matches can be identified. */
622
+ of the match so different matches can be identified.
623
+ This code is similar to codeInput.plugins.SelectTokenCallbacks.SelectedTokenState.updateSelectedTokens*/
531
624
  highlightMatch(matchID, currentElement, startIndex, endIndex) {
532
625
  for(let i = 0; i < currentElement.childNodes.length; i++) {
533
626
  let childElement = currentElement.childNodes[i];
@@ -535,6 +628,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
535
628
 
536
629
  let noInnerElements = false;
537
630
  if(childElement.nodeType == 3) {
631
+ // Text node
538
632
  if(i + 1 < currentElement.childNodes.length && currentElement.childNodes[i+1].nodeType == 3) {
539
633
  // Can merge with next text node
540
634
  currentElement.childNodes[i+1].textContent = childElement.textContent + currentElement.childNodes[i+1].textContent; // Merge textContent with next node
@@ -547,7 +641,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
547
641
 
548
642
  let replacementElement = document.createElement("span");
549
643
  replacementElement.textContent = childText;
550
- replacementElement.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
644
+ replacementElement.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
551
645
 
552
646
  currentElement.replaceChild(replacementElement, childElement);
553
647
  childElement = replacementElement;
@@ -562,7 +656,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
562
656
  let startSpan = document.createElement("span");
563
657
  startSpan.classList.add("code-input_find-and-replace_find-match"); // Highlighted
564
658
  startSpan.setAttribute("data-code-input_find-and-replace_match-id", matchID);
565
- startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
659
+ startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
566
660
  startSpan.textContent = childText.substring(0, endIndex);
567
661
  if(startSpan.textContent[0] == "\n") {
568
662
  // Newline at start - make clear
@@ -597,7 +691,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
597
691
  // Match starts and ends in childElement - highlight middle part
598
692
  // Text node - highlight last part
599
693
  let startSpan = document.createElement("span");
600
- startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
694
+ startSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
601
695
  startSpan.textContent = childText.substring(0, startIndex);
602
696
 
603
697
  let middleText = childText.substring(startIndex, endIndex);
@@ -610,7 +704,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
610
704
  }
611
705
 
612
706
  let endSpan = document.createElement("span");
613
- endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
707
+ endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
614
708
  endSpan.textContent = childText.substring(endIndex);
615
709
 
616
710
  childElement.insertAdjacentElement('beforebegin', startSpan);
@@ -624,7 +718,7 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
624
718
  let endSpan = document.createElement("span");
625
719
  endSpan.classList.add("code-input_find-and-replace_find-match"); // Highlighted
626
720
  endSpan.setAttribute("data-code-input_find-and-replace_match-id", matchID);
627
- endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove
721
+ endSpan.classList.add("code-input_find-and-replace_temporary-span"); // Can remove span later
628
722
  endSpan.textContent = childText.substring(startIndex);
629
723
  if(endSpan.textContent[0] == "\n") {
630
724
  // Newline at start - make clear
@@ -649,4 +743,4 @@ codeInput.plugins.FindAndReplace.FindMatchState = class {
649
743
  endIndex -= childText.length;
650
744
  }
651
745
  }
652
- }
746
+ }
@@ -1 +1 @@
1
- codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;constructor(a=!0,b=!0){super([]),this.useCtrlF=a,this.useCtrlH=b}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?"Search for matches in your code.":0>=a.findMatchState.numMatches?"No matches.":1==a.findMatchState.numMatches?"1 match found.":`${a.findMatchState.focusedMatchID+1} of ${a.findMatchState.numMatches} matches.`}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent="Error: "+c[c.length-1])}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),a.textarea.focus(),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){if(a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null){const c=a.textareaElement,d=document.createElement("div"),e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code"),i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),d.appendChild(l),d.appendChild(e),d.appendChild(g),d.appendChild(f),d.appendChild(h),j.appendChild(k),j.appendChild(i),d.appendChild(j),d.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder="Find",f.setAttribute("type","checkbox"),f.title="Match Case Sensitive",f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title="Use JavaScript Regular Expression",g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText="Replace",i.spellcheck=!1,i.placeholder="Replace with",m.innerText="\u2193",m.title="Find Next Occurence",n.innerText="\u2191",n.title="Find Previous Occurence",o.className="code-input_find-and-replace_button-hidden",o.innerText="Replace",o.title="Replace This Occurence",p.className="code-input_find-and-replace_button-hidden",p.innerText="Replace All",p.title="Replace All Occurences",m.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.nextMatch(),this.updateMatchDescription(d)}),n.addEventListener("click",()=>{event.preventDefault(),d.findMatchState.previousMatch(),this.updateMatchDescription(d)}),o.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceOnce(i.value),o.focus()}),p.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),d.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),d.codeInput=a,d.textarea=c,d.findInput=e,d.findCaseSensitiveCheckbox=f,d.findRegExpCheckbox=g,d.matchDescription=h,d.replaceInput=i,d.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(d,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(d,a,b)}),e.addEventListener("input",()=>{this.updateFindMatches(d)}),f.addEventListener("click",()=>{this.updateFindMatches(d)}),g.addEventListener("click",()=>{this.updateFindMatches(d)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(d,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(d,a,b)}),a.dialogContainerElement.appendChild(d),a.pluginData.findAndReplace={dialog:d},e.focus(),b&&j.setAttribute("open",!0),d.selectionStart=a.textareaElement.selectionStart,d.selectionEnd=a.textareaElement.selectionEnd}else a.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog"),a.pluginData.findAndReplace.dialog.findInput.focus(),b?a.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open",!0):a.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open"),this.updateFindMatches(a.pluginData.findAndReplace.dialog),a.pluginData.findAndReplace.dialog.selectionStart=a.textareaElement.selectionStart,a.pluginData.findAndReplace.dialog.selectionEnd=a.textareaElement.selectionEnd}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError("Causes an infinite loop");let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a))}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a)}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
1
+ codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;findMatchesOnValueChange=!0;instructions={start:"Search for matches in your code.",none:"No matches",oneFound:"1 match found.",matchIndex:(a,b)=>`${a} of ${b} matches.`,error:a=>`Error: ${a}`,infiniteLoopError:"Causes an infinite loop",closeDialog:"Close Dialog and Return to Editor",findPlaceholder:"Find",findCaseSensitive:"Match Case Sensitive",findRegExp:"Use JavaScript Regular Expression",replaceTitle:"Replace",replacePlaceholder:"Replace with",findNext:"Find Next Occurrence",findPrevious:"Find Previous Occurrence",replaceActionShort:"Replace",replaceAction:"Replace This Occurrence",replaceAllActionShort:"Replace All",replaceAllAction:"Replace All Occurrences"};constructor(a=!0,b=!0,c={}){super([]),this.useCtrlF=a,this.useCtrlH=b,this.addTranslations(this.instructions,c)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?this.instructions.start:0>=a.findMatchState.numMatches?this.instructions.none:1==a.findMatchState.numMatches?this.instructions.oneFound:this.instructions.matchIndex(a.findMatchState.focusedMatchID+1,a.findMatchState.numMatches)}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent=this.instructions.error(c[c.length-1]))}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),a.replaceInput.focus(),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),this.findMatchesOnValueChange=!1,a.findInput.focus(),a.findInput.selectionStart=0,a.findInput.selectionEnd=a.findInput.value.length,document.execCommand("insertText",!1,a.findInput.value),this.findMatchesOnValueChange=!0,a.textarea.focus(),a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){let c;if(null==a.pluginData.findAndReplace||null==a.pluginData.findAndReplace.dialog){const d=a.textareaElement;c=document.createElement("div");const e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code");h.setAttribute("aria-live","assertive");const i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");if(q.setAttribute("tabindex",0),q.setAttribute("title",this.instructions.closeDialog),l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),c.appendChild(l),c.appendChild(e),c.appendChild(g),c.appendChild(f),c.appendChild(h),j.appendChild(k),j.appendChild(i),c.appendChild(j),c.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder=this.instructions.findPlaceholder,f.setAttribute("type","checkbox"),f.title=this.instructions.findCaseSensitive,f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title=this.instructions.findRegExp,g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText=this.instructions.replaceTitle,i.spellcheck=!1,i.placeholder=this.instructions.replacePlaceholder,m.innerText="\u2193",m.title=this.instructions.findNext,n.innerText="\u2191",n.title=this.instructions.findPrevious,o.className="code-input_find-and-replace_button-hidden",o.innerText=this.instructions.replaceActionShort,o.title=this.instructions.replaceAction,o.addEventListener("focus",()=>{j.setAttribute("open",!0)}),p.className="code-input_find-and-replace_button-hidden",p.innerText=this.instructions.replaceAllActionShort,p.title=this.instructions.replaceAllAction,p.addEventListener("focus",()=>{j.setAttribute("open",!0)}),m.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.nextMatch(),this.updateMatchDescription(c)}),n.addEventListener("click",()=>{event.preventDefault(),c.findMatchState.previousMatch(),this.updateMatchDescription(c)}),o.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceOnce(i.value),c.focus()}),p.addEventListener("click",a=>{a.preventDefault(),c.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),c.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),c.codeInput=a,c.textarea=d,c.findInput=e,c.findCaseSensitiveCheckbox=f,c.findRegExpCheckbox=g,c.matchDescription=h,c.replaceInput=i,c.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("input",()=>{c.classList.contains("code-input_find-and-replace_hidden-dialog")?this.showPrompt(c.codeInput,!0):!c.replaceDropdown.hasAttribute("open")&&c.replaceDropdown.setAttribute("open",!0)}),c.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(c,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(c,a,b)}),e.addEventListener("input",()=>{this.findMatchesOnValueChange&&this.updateFindMatches(c),c.classList.contains("code-input_find-and-replace_hidden-dialog")&&this.showPrompt(c.codeInput,!1)}),f.addEventListener("click",()=>{this.updateFindMatches(c)}),g.addEventListener("click",()=>{this.updateFindMatches(c)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(c,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(c,a,b)}),q.addEventListener("keypress",b=>{("Space"==b.key||"Enter"==b.key)&&this.cancelPrompt(c,a,b)}),a.dialogContainerElement.appendChild(c),a.pluginData.findAndReplace={dialog:c},e.focus(),b&&j.setAttribute("open",!0),c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}}else c=a.pluginData.findAndReplace.dialog,c.classList.remove("code-input_find-and-replace_hidden-dialog"),c.removeAttribute("inert"),c.setAttribute("tabindex",0),c.removeAttribute("aria-hidden"),c.findInput.focus(),b?c.replaceDropdown.setAttribute("open",!0):c.replaceDropdown.removeAttribute("open");if(c.selectionStart=a.textareaElement.selectionStart,c.selectionEnd=a.textareaElement.selectionEnd,c.selectionStart<c.selectionEnd){let b=a.textareaElement.value.substring(c.selectionStart,c.selectionEnd);c.findInput.focus(),c.findInput.selectionStart=0,c.findInput.selectionEnd=c.findInput.value.length,document.execCommand("insertText",!1,b)}this.updateFindMatches(c)}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError(this.instructions.infiniteLoopError);let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0)}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.handleEventsFromTextarea=!1,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a),this.codeInput.handleEventsFromTextarea=!0}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
@@ -5,13 +5,20 @@
5
5
  codeInput.plugins.GoToLine = class extends codeInput.Plugin {
6
6
  useCtrlG = false;
7
7
 
8
+ instructions = {
9
+ closeDialog: "Close Dialog and Return to Editor",
10
+ input: "Line:Column / Line no. then Enter",
11
+ };
12
+
8
13
  /**
9
14
  * Create a go-to-line command plugin to pass into a template
10
15
  * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
16
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
11
17
  */
12
- constructor(useCtrlG = true) {
18
+ constructor(useCtrlG = true, instructionTranslations = {}) {
13
19
  super([]); // No observed attributes
14
20
  this.useCtrlG = useCtrlG;
21
+ this.addTranslations(this.instructions, instructionTranslations);
15
22
  }
16
23
 
17
24
  /* Add keystroke events */
@@ -61,7 +68,12 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
61
68
  /* Called with a dialog box keyup event to close and clear the dialog box */
62
69
  cancelPrompt(dialog, event) {
63
70
  event.preventDefault();
71
+ dialog.codeInput.handleEventsFromTextarea = false;
64
72
  dialog.textarea.focus();
73
+ dialog.codeInput.handleEventsFromTextarea = true;
74
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
75
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
76
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
65
77
 
66
78
  // Remove dialog after animation
67
79
  dialog.classList.add('code-input_go-to-line_hidden-dialog');
@@ -79,13 +91,15 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
79
91
  const dialog = document.createElement('div');
80
92
  const input = document.createElement('input');
81
93
  const cancel = document.createElement('span');
94
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
95
+ cancel.setAttribute("title", this.instructions.closeDialog);
82
96
 
83
97
  dialog.appendChild(input);
84
98
  dialog.appendChild(cancel);
85
99
 
86
100
  dialog.className = 'code-input_go-to-line_dialog';
87
101
  input.spellcheck = false;
88
- input.placeholder = "Line:Column / Line no. then Enter";
102
+ input.placeholder = this.instructions.input;
89
103
  dialog.codeInput = codeInput;
90
104
  dialog.textarea = textarea;
91
105
  dialog.input = input;
@@ -97,12 +111,16 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
97
111
 
98
112
  input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
99
113
  cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
114
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, event); });
100
115
 
101
116
  codeInput.dialogContainerElement.appendChild(dialog);
102
117
  codeInput.pluginData.goToLine = {dialog: dialog};
103
118
  input.focus();
104
119
  } else {
105
120
  codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
121
+ codeInput.pluginData.goToLine.dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
122
+ codeInput.pluginData.goToLine.dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
123
+ codeInput.pluginData.goToLine.dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
106
124
  codeInput.pluginData.goToLine.dialog.input.focus();
107
125
  }
108
126
  }
@@ -1 +1 @@
1
- codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;constructor(a=!0){super([]),this.useCtrlG=a}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.textarea.focus(),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder="Line:Column / Line no. then Enter",c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};
1
+ codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;instructions={closeDialog:"Close Dialog and Return to Editor",input:"Line:Column / Line no. then Enter"};constructor(a=!0,b={}){super([]),this.useCtrlG=a,this.addTranslations(this.instructions,b)}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.codeInput.handleEventsFromTextarea=!1,a.textarea.focus(),a.codeInput.handleEventsFromTextarea=!0,a.setAttribute("inert",!0),a.setAttribute("tabindex",-1),a.setAttribute("aria-hidden",!0),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");e.setAttribute("tabindex",0),e.setAttribute("title",this.instructions.closeDialog),c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder=this.instructions.input,c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),e.addEventListener("keypress",a=>{("Space"==a.key||"Enter"==a.key)&&this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.removeAttribute("inert"),a.pluginData.goToLine.dialog.setAttribute("tabindex",0),a.pluginData.goToLine.dialog.removeAttribute("aria-hidden"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};