tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

natural-sort.js (5183B)


      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/. */
      4 
      5 /*
      6 * Based on the Natural Sort algorithm for Javascript - Version 0.8.1 - adapted
      7 * for Firefox DevTools and released under the MIT license.
      8 *
      9 * Author: Jim Palmer (based on chunking idea from Dave Koelle)
     10 *
     11 * Repository:
     12 *   https://github.com/overset/javascript-natural-sort/
     13 */
     14 
     15 "use strict";
     16 
     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+$/;
     26 
     27 // If a string contains any of these, we'll try to parse it as a Date
     28 const dateKeywords = [
     29  "mon",
     30  "tues",
     31  "wed",
     32  "thur",
     33  "fri",
     34  "sat",
     35  "sun",
     36 
     37  "jan",
     38  "feb",
     39  "mar",
     40  "apr",
     41  "may",
     42  "jun",
     43  "jul",
     44  "aug",
     45  "sep",
     46  "oct",
     47  "nov",
     48  "dec",
     49 ];
     50 
     51 /**
     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.
     56 */
     57 function tryParseDate(str) {
     58  const lowerCaseStr = str.toLowerCase();
     59  return (
     60    !versionRx.test(str) &&
     61    (numericDateRx.test(str) ||
     62      dateKeywords.some(s => lowerCaseStr.includes(s))) &&
     63    Date.parse(str)
     64  );
     65 }
     66 
     67 /**
     68 * Sort numbers, strings, IP Addresses, Dates, Filenames, version numbers etc.
     69 * "the way humans do."
     70 *
     71 * @param  {object} a
     72 *         Passed in by Array.sort(a, b)
     73 * @param  {object} 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?
     82 */
     83 // eslint-disable-next-line complexity
     84 function naturalSort(a = "", b = "", sessionString, insensitive = false) {
     85  // Ensure we are working with trimmed strings
     86  a = (a + "").trim();
     87  b = (b + "").trim();
     88 
     89  if (insensitive) {
     90    a = a.toLowerCase();
     91    b = b.toLowerCase();
     92    sessionString = sessionString.toLowerCase();
     93  }
     94 
     95  // Chunk/tokenize - Here we split the strings into arrays or strings and
     96  // numbers.
     97  const aChunks = a
     98    .replace(tokenizeNumbersRx, "\0$1\0")
     99    .replace(startsWithNullRx, "")
    100    .replace(endsWithNullRx, "")
    101    .split("\0");
    102  const bChunks = b
    103    .replace(tokenizeNumbersRx, "\0$1\0")
    104    .replace(startsWithNullRx, "")
    105    .replace(endsWithNullRx, "")
    106    .split("\0");
    107 
    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);
    111 
    112  if (
    113    (aHexOrDate || bHexOrDate) &&
    114    (a === sessionString || b === sessionString)
    115  ) {
    116    // We have a date and a session string. Move "Session" above the date
    117    // (for session cookies)
    118    if (a === sessionString) {
    119      return -1;
    120    } else if (b === sessionString) {
    121      return 1;
    122    }
    123  }
    124 
    125  // Try and sort Hex codes or Dates.
    126  if (aHexOrDate && bHexOrDate) {
    127    if (aHexOrDate < bHexOrDate) {
    128      return -1;
    129    } else if (aHexOrDate > bHexOrDate) {
    130      return 1;
    131    }
    132    return 0;
    133  }
    134 
    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);
    139 
    140  for (let i = 0; i < maxLen; i++) {
    141    const aChunk = normalizeChunk(aChunks[i] || "", aChunksLength);
    142    const bChunk = normalizeChunk(bChunks[i] || "", bChunksLength);
    143 
    144    // Handle numeric vs string comparison - number < string
    145    if (isNaN(aChunk) !== isNaN(bChunk)) {
    146      return isNaN(aChunk) ? 1 : -1;
    147    }
    148 
    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);
    154    }
    155    if (aChunk < bChunk) {
    156      return -1;
    157    } else if (aChunk > bChunk) {
    158      return 1;
    159    }
    160  }
    161  return null;
    162 }
    163 
    164 // Normalize spaces; find floats not starting with '0', string or 0 if not
    165 // defined
    166 const normalizeChunk = function (str, length) {
    167  return (
    168    ((!str.match(startsWithZeroRx) || length == 1) && parseFloat(str)) ||
    169    str.replace(whitespaceRx, " ").trim() ||
    170    0
    171  );
    172 };
    173 
    174 exports.naturalSortCaseSensitive = function naturalSortCaseSensitive(
    175  a,
    176  b,
    177  sessionString
    178 ) {
    179  return naturalSort(a, b, sessionString, false);
    180 };
    181 
    182 exports.naturalSortCaseInsensitive = function naturalSortCaseInsensitive(
    183  a,
    184  b,
    185  sessionString
    186 ) {
    187  return naturalSort(a, b, sessionString, true);
    188 };