diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/shared/natural-sort.js | |
parent | Initial commit. (diff) | |
download | firefox-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.js | 188 |
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); +}; |