1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * Based on the Natural Sort algorithm for Javascript - Version 0.8.1 - adapted
7 * for Firefox DevTools and released under the MIT license.
9 * Author: Jim Palmer (based on chunking idea from Dave Koelle)
12 * https://github.com/overset/javascript-natural-sort/
17 const tokenizeNumbersRx =
18 /(^([+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|^0x[\da-fA-F]+$|\d+)/g;
19 const hexRx = /^0x[0-9a-f]+$/i;
20 const startsWithNullRx = /^\0/;
21 const endsWithNullRx = /\0$/;
22 const whitespaceRx = /\s+/g;
23 const startsWithZeroRx = /^0/;
24 const versionRx = /^([\w-]+-)?\d+\.\d+\.\d+$/;
25 const numericDateRx = /^\d+[- /]\d+[- /]\d+$/;
27 // If a string contains any of these, we'll try to parse it as a Date
28 const dateKeywords = [
52 * Figures whether a given string should be considered by naturalSort to be a
53 * Date, and returns the Date's timestamp if so. Some Date formats, like
54 * single numbers and MM.DD.YYYY, are not supported due to conflicts with things
55 * like version numbers.
57 function tryParseDate(str) {
58 const lowerCaseStr = str.toLowerCase();
60 !versionRx.test(str) &&
61 (numericDateRx.test(str) ||
62 dateKeywords.some(s => lowerCaseStr.includes(s))) &&
68 * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
69 * "the way humans do."
72 * Passed in by Array.sort(a, b)
74 * Passed in by Array.sort(a, b)
75 * @param {String} sessionString
76 * Client-side value of storage-expires-session l10n string.
77 * Since this function can be called from both the client and the server,
78 * and given that client and server might have different locale, we can't compute
79 * the localized string directly from here.
80 * @param {Boolean} insensitive
81 * Should the search be case insensitive?
83 // eslint-disable-next-line complexity
84 function naturalSort(a = "", b = "", sessionString, insensitive = false) {
85 // Ensure we are working with trimmed strings
92 sessionString = sessionString.toLowerCase();
95 // Chunk/tokenize - Here we split the strings into arrays or strings and
98 .replace(tokenizeNumbersRx, "\0$1\0")
99 .replace(startsWithNullRx, "")
100 .replace(endsWithNullRx, "")
103 .replace(tokenizeNumbersRx, "\0$1\0")
104 .replace(startsWithNullRx, "")
105 .replace(endsWithNullRx, "")
108 // Hex or date detection.
109 const aHexOrDate = parseInt(a.match(hexRx), 16) || tryParseDate(a);
110 const bHexOrDate = parseInt(b.match(hexRx), 16) || tryParseDate(b);
113 (aHexOrDate || bHexOrDate) &&
114 (a === sessionString || b === sessionString)
116 // We have a date and a session string. Move "Session" above the date
117 // (for session cookies)
118 if (a === sessionString) {
120 } else if (b === sessionString) {
125 // Try and sort Hex codes or Dates.
126 if (aHexOrDate && bHexOrDate) {
127 if (aHexOrDate < bHexOrDate) {
129 } else if (aHexOrDate > bHexOrDate) {
135 // Natural sorting through split numeric strings and default strings
136 const aChunksLength = aChunks.length;
137 const bChunksLength = bChunks.length;
138 const maxLen = Math.max(aChunksLength, bChunksLength);
140 for (let i = 0; i < maxLen; i++) {
141 const aChunk = normalizeChunk(aChunks[i] || "", aChunksLength);
142 const bChunk = normalizeChunk(bChunks[i] || "", bChunksLength);
144 // Handle numeric vs string comparison - number < string
145 if (isNaN(aChunk) !== isNaN(bChunk)) {
146 return isNaN(aChunk) ? 1 : -1;
149 // If unicode use locale comparison
150 // eslint-disable-next-line no-control-regex
151 if (/[^\x00-\x80]/.test(aChunk + bChunk) && aChunk.localeCompare) {
152 const comp = aChunk.localeCompare(bChunk);
153 return comp / Math.abs(comp);
155 if (aChunk < bChunk) {
157 } else if (aChunk > bChunk) {
164 // Normalize spaces; find floats not starting with '0', string or 0 if not
166 const normalizeChunk = function (str, length) {
168 ((!str.match(startsWithZeroRx) || length == 1) && parseFloat(str)) ||
169 str.replace(whitespaceRx, " ").trim() ||
174 exports.naturalSortCaseSensitive = function naturalSortCaseSensitive(
179 return naturalSort(a, b, sessionString, false);
182 exports.naturalSortCaseInsensitive = function naturalSortCaseInsensitive(
187 return naturalSort(a, b, sessionString, true);