Просмотр исходного кода

Implement `replaceAll` utility function

add #8331 release notes
thatmattlove 4 лет назад
Родитель
Сommit
6c1507c88c

+ 1 - 0
docs/release-notes/version-3.1.md

@@ -6,6 +6,7 @@
 
 * [#8548](https://github.com/netbox-community/netbox/issues/8548) - Fix display of VC members when position is zero
 * [#8561](https://github.com/netbox-community/netbox/issues/8561) - Include option to connect a rear port to a console port
+* [#8331](https://github.com/netbox-community/netbox/issues/8331) - Implement `replaceAll` string utility function to improve browser compatibility
 
 ---
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/status.js


+ 12 - 9
netbox/project-static/src/select/api/apiSelect.ts

@@ -8,11 +8,12 @@ import { DynamicParamsMap } from './dynamicParams';
 import { isStaticParams, isOption } from './types';
 import {
   hasMore,
-  isTruthy,
   hasError,
-  getElement,
+  isTruthy,
   getApiData,
+  getElement,
   isApiError,
+  replaceAll,
   createElement,
   uniqueByProperty,
   findFirstAdjacent,
@@ -461,7 +462,7 @@ export class APISelect {
       // Set any primitive k/v pairs as data attributes on each option.
       for (const [k, v] of Object.entries(result)) {
         if (!['id', 'slug'].includes(k) && ['string', 'number', 'boolean'].includes(typeof v)) {
-          const key = k.replace(/_/g, '-');
+          const key = replaceAll(k, '_', '-');
           data[key] = String(v);
         }
         // Set option to disabled if the result contains a matching key and is truthy.
@@ -659,7 +660,7 @@ export class APISelect {
     for (const [key, value] of this.pathValues.entries()) {
       for (const result of this.url.matchAll(new RegExp(`({{${key}}})`, 'g'))) {
         if (isTruthy(value)) {
-          url = url.replace(result[1], value.toString());
+          url = replaceAll(url, result[1], value.toString());
         }
       }
     }
@@ -741,7 +742,7 @@ export class APISelect {
    * @param id DOM ID of the other element.
    */
   private updatePathValues(id: string): void {
-    const key = id.replace(/^id_/gi, '');
+    const key = replaceAll(id, /^id_/i, '');
     const element = getElement<HTMLSelectElement>(`id_${key}`);
     if (element !== null) {
       // If this element's URL contains Django template tags ({{), replace the template tag
@@ -919,16 +920,18 @@ export class APISelect {
         style.setAttribute('data-netbox', id);
 
         // Scope the CSS to apply both the list item and the selected item.
-        style.innerHTML = `
+        style.innerHTML = replaceAll(
+          `
   div.ss-values div.ss-value[data-id="${id}"],
   div.ss-list div.ss-option:not(.ss-disabled)[data-id="${id}"]
    {
     background-color: ${bg} !important;
     color: ${fg} !important;
   }
-              `
-          .replace(/\n/g, '')
-          .trim();
+              `,
+          '\n',
+          '',
+        ).trim();
 
         // Add the style element to the DOM.
         document.head.appendChild(style);

+ 3 - 3
netbox/project-static/src/tables/interfaceTable.ts

@@ -1,4 +1,4 @@
-import { getElements, findFirstAdjacent } from '../util';
+import { getElements, replaceAll, findFirstAdjacent } from '../util';
 
 type InterfaceState = 'enabled' | 'disabled';
 type ShowHide = 'show' | 'hide';
@@ -105,9 +105,9 @@ class ButtonState {
    */
   private toggleButton(): void {
     if (this.buttonState === 'show') {
-      this.button.innerText = this.button.innerText.replace(/Show/g, 'Hide');
+      this.button.innerText = replaceAll(this.button.innerText, 'Show', 'Hide');
     } else if (this.buttonState === 'hide') {
-      this.button.innerText = this.button.innerText.replace(/Hide/g, 'Show');
+      this.button.innerText = replaceAll(this.button.innerHTML, 'Hide', 'Show');
     }
   }
 

+ 47 - 1
netbox/project-static/src/util.ts

@@ -315,7 +315,7 @@ export function* getRowValues(table: HTMLTableRowElement): Generator<string> {
   for (const element of table.querySelectorAll<HTMLTableCellElement>('td')) {
     if (element !== null) {
       if (isTruthy(element.innerText) && element.innerText !== '—') {
-        yield element.innerText.replace(/[\n\r]/g, '').trim();
+        yield replaceAll(element.innerText, '[\n\r]', '').trim();
       }
     }
   }
@@ -436,3 +436,49 @@ export function uniqueByProperty<T extends unknown, P extends keyof T>(arr: T[],
   }
   return Array.from(baseMap.values());
 }
+
+/**
+ * Replace all occurrences of a pattern with a replacement string.
+ *
+ * This is a browser-compatibility-focused drop-in replacement for `String.prototype.replaceAll()`,
+ * introduced in ES2021.
+ *
+ * @param input string to be processed.
+ * @param pattern regex pattern string or RegExp object to search for.
+ * @param replacement replacement substring with which `pattern` matches will be replaced.
+ * @returns processed version of `input`.
+ */
+export function replaceAll(input: string, pattern: string | RegExp, replacement: string): string {
+  // Ensure input is a string.
+  if (typeof input !== 'string') {
+    throw new TypeError("replaceAll 'input' argument must be a string");
+  }
+  // Ensure pattern is a string or RegExp.
+  if (typeof pattern !== 'string' && !(pattern instanceof RegExp)) {
+    throw new TypeError("replaceAll 'pattern' argument must be a string or RegExp instance");
+  }
+  // Ensure replacement is able to be stringified.
+  switch (typeof replacement) {
+    case 'boolean':
+      replacement = String(replacement);
+      break;
+    case 'number':
+      replacement = String(replacement);
+      break;
+    case 'string':
+      break;
+    default:
+      throw new TypeError("replaceAll 'replacement' argument must be stringifyable");
+  }
+
+  if (pattern instanceof RegExp) {
+    // Add global flag to existing RegExp object and deduplicate
+    const flags = Array.from(new Set([...pattern.flags.split(''), 'g'])).join('');
+    pattern = new RegExp(pattern.source, flags);
+  } else {
+    // Create a RegExp object with the global flag set.
+    pattern = new RegExp(pattern, 'g');
+  }
+
+  return input.replace(pattern, replacement);
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов