summaryrefslogtreecommitdiffstats
path: root/devtools/shared/natural-sort.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/shared/natural-sort.js
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/natural-sort.js')
-rw-r--r--devtools/shared/natural-sort.js188
1 files changed, 188 insertions, 0 deletions
diff --git a/devtools/shared/natural-sort.js b/devtools/shared/natural-sort.js
new file mode 100644
index 0000000000..0b6a30db5f
--- /dev/null
+++ b/devtools/shared/natural-sort.js
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Based on the Natural Sort algorithm for Javascript - Version 0.8.1 - adapted
+ * for Firefox DevTools and released under the MIT license.
+ *
+ * Author: Jim Palmer (based on chunking idea from Dave Koelle)
+ *
+ * Repository:
+ * https://github.com/overset/javascript-natural-sort/
+ */
+
+"use strict";
+
+const tokenizeNumbersRx =
+ /(^([+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
+const hexRx = /^0x[0-9a-f]+$/i;
+const startsWithNullRx = /^\0/;
+const endsWithNullRx = /\0$/;
+const whitespaceRx = /\s+/g;
+const startsWithZeroRx = /^0/;
+const versionRx = /^([\w-]+-)?\d+\.\d+\.\d+$/;
+const numericDateRx = /^\d+[- /]\d+[- /]\d+$/;
+
+// If a string contains any of these, we'll try to parse it as a Date
+const dateKeywords = [
+ "mon",
+ "tues",
+ "wed",
+ "thur",
+ "fri",
+ "sat",
+ "sun",
+
+ "jan",
+ "feb",
+ "mar",
+ "apr",
+ "may",
+ "jun",
+ "jul",
+ "aug",
+ "sep",
+ "oct",
+ "nov",
+ "dec",
+];
+
+/**
+ * Figures whether a given string should be considered by naturalSort to be a
+ * Date, and returns the Date's timestamp if so. Some Date formats, like
+ * single numbers and MM.DD.YYYY, are not supported due to conflicts with things
+ * like version numbers.
+ */
+function tryParseDate(str) {
+ const lowerCaseStr = str.toLowerCase();
+ return (
+ !versionRx.test(str) &&
+ (numericDateRx.test(str) ||
+ dateKeywords.some(s => lowerCaseStr.includes(s))) &&
+ Date.parse(str)
+ );
+}
+
+/**
+ * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
+ * "the way humans do."
+ *
+ * @param {Object} a
+ * Passed in by Array.sort(a, b)
+ * @param {Object} b
+ * Passed in by Array.sort(a, b)
+ * @param {String} sessionString
+ * Client-side value of storage-expires-session l10n string.
+ * Since this function can be called from both the client and the server,
+ * and given that client and server might have different locale, we can't compute
+ * the localized string directly from here.
+ * @param {Boolean} insensitive
+ * Should the search be case insensitive?
+ */
+// eslint-disable-next-line complexity
+function naturalSort(a = "", b = "", sessionString, insensitive = false) {
+ // Ensure we are working with trimmed strings
+ a = (a + "").trim();
+ b = (b + "").trim();
+
+ if (insensitive) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ sessionString = sessionString.toLowerCase();
+ }
+
+ // Chunk/tokenize - Here we split the strings into arrays or strings and
+ // numbers.
+ const aChunks = a
+ .replace(tokenizeNumbersRx, "\0$1\0")
+ .replace(startsWithNullRx, "")
+ .replace(endsWithNullRx, "")
+ .split("\0");
+ const bChunks = b
+ .replace(tokenizeNumbersRx, "\0$1\0")
+ .replace(startsWithNullRx, "")
+ .replace(endsWithNullRx, "")
+ .split("\0");
+
+ // Hex or date detection.
+ const aHexOrDate = parseInt(a.match(hexRx), 16) || tryParseDate(a);
+ const bHexOrDate = parseInt(b.match(hexRx), 16) || tryParseDate(b);
+
+ if (
+ (aHexOrDate || bHexOrDate) &&
+ (a === sessionString || b === sessionString)
+ ) {
+ // We have a date and a session string. Move "Session" above the date
+ // (for session cookies)
+ if (a === sessionString) {
+ return -1;
+ } else if (b === sessionString) {
+ return 1;
+ }
+ }
+
+ // Try and sort Hex codes or Dates.
+ if (aHexOrDate && bHexOrDate) {
+ if (aHexOrDate < bHexOrDate) {
+ return -1;
+ } else if (aHexOrDate > bHexOrDate) {
+ return 1;
+ }
+ return 0;
+ }
+
+ // Natural sorting through split numeric strings and default strings
+ const aChunksLength = aChunks.length;
+ const bChunksLength = bChunks.length;
+ const maxLen = Math.max(aChunksLength, bChunksLength);
+
+ for (let i = 0; i < maxLen; i++) {
+ const aChunk = normalizeChunk(aChunks[i] || "", aChunksLength);
+ const bChunk = normalizeChunk(bChunks[i] || "", bChunksLength);
+
+ // Handle numeric vs string comparison - number < string
+ if (isNaN(aChunk) !== isNaN(bChunk)) {
+ return isNaN(aChunk) ? 1 : -1;
+ }
+
+ // If unicode use locale comparison
+ // eslint-disable-next-line no-control-regex
+ if (/[^\x00-\x80]/.test(aChunk + bChunk) && aChunk.localeCompare) {
+ const comp = aChunk.localeCompare(bChunk);
+ return comp / Math.abs(comp);
+ }
+ if (aChunk < bChunk) {
+ return -1;
+ } else if (aChunk > bChunk) {
+ return 1;
+ }
+ }
+ return null;
+}
+
+// Normalize spaces; find floats not starting with '0', string or 0 if not
+// defined
+const normalizeChunk = function (str, length) {
+ return (
+ ((!str.match(startsWithZeroRx) || length == 1) && parseFloat(str)) ||
+ str.replace(whitespaceRx, " ").trim() ||
+ 0
+ );
+};
+
+exports.naturalSortCaseSensitive = function naturalSortCaseSensitive(
+ a,
+ b,
+ sessionString
+) {
+ return naturalSort(a, b, sessionString, false);
+};
+
+exports.naturalSortCaseInsensitive = function naturalSortCaseInsensitive(
+ a,
+ b,
+ sessionString
+) {
+ return naturalSort(a, b, sessionString, true);
+};