Files
frontend/src/common/string/compare.ts
Kevin Lakotko 8cc762d839 Fix use of numeric option for collator (#25917)
* fix(string): use numeric option for collator

* test: add natural sort comparison tests
2025-06-30 18:00:45 +02:00

121 lines
2.5 KiB
TypeScript

import memoizeOne from "memoize-one";
import { isIPAddress } from "./is_ip_address";
const collator = memoizeOne(
(language: string | undefined) =>
new Intl.Collator(language, { numeric: true })
);
const caseInsensitiveCollator = memoizeOne(
(language: string | undefined) =>
new Intl.Collator(language, { sensitivity: "accent", numeric: true })
);
const fallbackStringCompare = (a: string, b: string) => {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
};
export const stringCompare = (
a: string,
b: string,
language: string | undefined = undefined
) => {
// @ts-ignore
if (Intl?.Collator) {
return collator(language).compare(a, b);
}
return fallbackStringCompare(a, b);
};
export const ipCompare = (a: string, b: string) => {
const aIsIpV4 = isIPAddress(a);
const bIsIpV4 = isIPAddress(b);
if (aIsIpV4 && bIsIpV4) {
return ipv4Compare(a, b);
}
if (!aIsIpV4 && !bIsIpV4) {
return ipV6Compare(a, b);
}
return aIsIpV4 ? -1 : 1;
};
export const caseInsensitiveStringCompare = (
a: string,
b: string,
language: string | undefined = undefined
) => {
// @ts-ignore
if (Intl?.Collator) {
return caseInsensitiveCollator(language).compare(a, b);
}
return fallbackStringCompare(a.toLowerCase(), b.toLowerCase());
};
export const orderCompare = (order: string[]) => (a: string, b: string) => {
const idxA = order.indexOf(a);
const idxB = order.indexOf(b);
if (idxA === idxB) {
return 0;
}
if (idxA === -1) {
return 1;
}
if (idxB === -1) {
return -1;
}
return idxA - idxB;
};
function ipv4Compare(a: string, b: string) {
const num1 = Number(
a
.split(".")
.map((num) => num.padStart(3, "0"))
.join("")
);
const num2 = Number(
b
.split(".")
.map((num) => num.padStart(3, "0"))
.join("")
);
return num1 - num2;
}
function ipV6Compare(a: string, b: string) {
const ipv6a = normalizeIPv6(a)
.split(":")
.map((part) => part.padStart(4, "0"))
.join("");
const ipv6b = normalizeIPv6(b)
.split(":")
.map((part) => part.padStart(4, "0"))
.join("");
return ipv6a.localeCompare(ipv6b);
}
function normalizeIPv6(ip) {
const parts = ip.split("::");
const head = parts[0].split(":");
const tail = parts[1] ? parts[1].split(":") : [];
const totalParts = 8;
const missing = totalParts - (head.length + tail.length);
const zeros = new Array(missing).fill("0");
return [...head, ...zeros, ...tail].join(":");
}