Index: frontend/node_modules/string-natural-compare/LICENSE.txt
===================================================================
--- frontend/node_modules/string-natural-compare/LICENSE.txt	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/string-natural-compare/LICENSE.txt	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015-2016 Nathan Woltman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
Index: frontend/node_modules/string-natural-compare/README.md
===================================================================
--- frontend/node_modules/string-natural-compare/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/string-natural-compare/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,109 @@
+# String Natural Compare
+
+[![NPM Version](https://img.shields.io/npm/v/string-natural-compare.svg)](https://www.npmjs.com/package/string-natural-compare)
+[![Build Status](https://travis-ci.org/nwoltman/string-natural-compare.svg?branch=master)](https://travis-ci.org/nwoltman/string-natural-compare)
+[![Coverage Status](https://coveralls.io/repos/nwoltman/string-natural-compare/badge.svg?branch=master)](https://coveralls.io/r/nwoltman/string-natural-compare?branch=master)
+[![Dependencies Status](https://img.shields.io/david/nwoltman/string-natural-compare)](https://david-dm.org/nwoltman/string-natural-compare)
+
+Compare alphanumeric strings the same way a human would, using a natural order algorithm (originally known as the [alphanum algorithm](http://davekoelle.com/alphanum.html)) where numeric characters are sorted based on their numeric values rather than their ASCII values.
+
+```
+Standard sorting:   Natural order sorting:
+    img1.png            img1.png
+    img10.png           img2.png
+    img12.png           img10.png
+    img2.png            img12.png
+```
+
+This module exports a function that returns a number indicating whether one string should come before, after, or is the same as another string.
+It can be used directly with the native [`.sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) array method.
+
+### Fast and Robust
+
+This module can compare strings containing any size of number and is heavily tested with a custom [benchmark suite](https://github.com/nwoltman/string-natural-compare/tree/master/benchmark) to make sure that it is as fast as possible.
+
+
+## Installation
+
+```sh
+npm install string-natural-compare --save
+# or
+yarn add string-natural-compare
+```
+
+
+## Usage
+
+#### `naturalCompare(strA, strB[, options])`
+
++ `strA` (_string_)
++ `strB` (_string_)
++ `options` (_object_) - Optional options object with the following options:
+  + `caseInsensitive` (_boolean_) - Set to `true` to compare strings case-insensitively. Default: `false`.
+  + `alphabet` (_string_) - A string of characters that define a custom character ordering. Default: `undefined`.
+
+```js
+const naturalCompare = require('string-natural-compare');
+
+// Simple, case-sensitive sorting
+const files = ['z1.doc', 'z10.doc', 'z17.doc', 'z2.doc', 'z23.doc', 'z3.doc'];
+files.sort(naturalCompare);
+// -> ['z1.doc', 'z2.doc', 'z3.doc', 'z10.doc', 'z17.doc', 'z23.doc']
+
+
+// Case-insensitive sorting
+const chars = ['B', 'C', 'a', 'd'];
+const naturalCompareCI = (a, b) => naturalCompare(a, b, {caseInsensitive: true});
+chars.sort(naturalCompareCI);
+// -> ['a', 'B', 'C', 'd']
+
+// Note:
+['a', 'A'].sort(naturalCompareCI); // -> ['a', 'A']
+['A', 'a'].sort(naturalCompareCI); // -> ['A', 'a']
+
+
+// Compare strings containing large numbers
+naturalCompare(
+  '1165874568735487968325787328996865',
+  '265812277985321589735871687040841'
+);
+// -> 1
+// (Other inputs with the same ordering as this example may yield a different number > 0)
+
+
+// Sorting an array of objects
+const hotelRooms = [
+  {street: '350 5th Ave', room: 'A-1021'},
+  {street: '350 5th Ave', room: 'A-21046-b'}
+];
+// Sort by street (case-insensitive), then by room (case-sensitive)
+hotelRooms.sort((a, b) => (
+  naturalCompare(a.street, b.street, {caseInsensitive: true}) ||
+  naturalCompare(a.room, b.room)
+));
+
+
+// When text transformation is needed or when doing a case-insensitive sort on a
+// large array of objects, it is best for performance to pre-compute the
+// transformed text and store it on the object. This way, the text will not need
+// to be transformed for every comparison while sorting.
+const cars = [
+  {make: 'Audi', model: 'R8'},
+  {make: 'Porsche', model: '911 Turbo S'}
+];
+// Sort by make, then by model (both case-insensitive)
+for (const car of cars) {
+  car.sortKey = (car.make + ' ' + car.model).toLowerCase();
+}
+cars.sort((a, b) => naturalCompare(a.sortKey, b.sortKey));
+
+
+// Using a custom alphabet (Russian alphabet)
+const russianOpts = {
+  alphabet: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя',
+};
+['Ё', 'А', 'б', 'Б'].sort((a, b) => naturalCompare(a, b, russianOpts));
+// -> ['А', 'Б', 'Ё', 'б']
+```
+
+**Note:** Putting numbers in the custom alphabet can cause undefined behaviour.
Index: frontend/node_modules/string-natural-compare/natural-compare.js
===================================================================
--- frontend/node_modules/string-natural-compare/natural-compare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/string-natural-compare/natural-compare.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,139 @@
+'use strict';
+
+const defaultAlphabetIndexMap = [];
+
+function isNumberCode(code) {
+  return code >= 48/* '0' */ && code <= 57/* '9' */;
+}
+
+function naturalCompare(a, b, opts) {
+  if (typeof a !== 'string') {
+    throw new TypeError(`The first argument must be a string. Received type '${typeof a}'`);
+  }
+  if (typeof b !== 'string') {
+    throw new TypeError(`The second argument must be a string. Received type '${typeof b}'`);
+  }
+
+  const lengthA = a.length;
+  const lengthB = b.length;
+  let indexA = 0;
+  let indexB = 0;
+  let alphabetIndexMap = defaultAlphabetIndexMap;
+  let firstDifferenceInLeadingZeros = 0;
+
+  if (opts) {
+    if (opts.caseInsensitive) {
+      a = a.toLowerCase();
+      b = b.toLowerCase();
+    }
+
+    if (opts.alphabet) {
+      alphabetIndexMap = buildAlphabetIndexMap(opts.alphabet);
+    }
+  }
+
+  while (indexA < lengthA && indexB < lengthB) {
+    let charCodeA = a.charCodeAt(indexA);
+    let charCodeB = b.charCodeAt(indexB);
+
+    if (isNumberCode(charCodeA)) {
+      if (!isNumberCode(charCodeB)) {
+        return charCodeA - charCodeB;
+      }
+
+      let numStartA = indexA;
+      let numStartB = indexB;
+
+      while (charCodeA === 48/* '0' */ && ++numStartA < lengthA) {
+        charCodeA = a.charCodeAt(numStartA);
+      }
+      while (charCodeB === 48/* '0' */ && ++numStartB < lengthB) {
+        charCodeB = b.charCodeAt(numStartB);
+      }
+
+      if (numStartA !== numStartB && firstDifferenceInLeadingZeros === 0) {
+        firstDifferenceInLeadingZeros = numStartA - numStartB;
+      }
+
+      let numEndA = numStartA;
+      let numEndB = numStartB;
+
+      while (numEndA < lengthA && isNumberCode(a.charCodeAt(numEndA))) {
+        ++numEndA;
+      }
+      while (numEndB < lengthB && isNumberCode(b.charCodeAt(numEndB))) {
+        ++numEndB;
+      }
+
+      let difference = numEndA - numStartA - numEndB + numStartB; // numA length - numB length
+      if (difference !== 0) {
+        return difference;
+      }
+
+      while (numStartA < numEndA) {
+        difference = a.charCodeAt(numStartA++) - b.charCodeAt(numStartB++);
+        if (difference !== 0) {
+          return difference;
+        }
+      }
+
+      indexA = numEndA;
+      indexB = numEndB;
+      continue;
+    }
+
+    if (charCodeA !== charCodeB) {
+      if (
+        charCodeA < alphabetIndexMap.length &&
+        charCodeB < alphabetIndexMap.length &&
+        alphabetIndexMap[charCodeA] !== -1 &&
+        alphabetIndexMap[charCodeB] !== -1
+      ) {
+        return alphabetIndexMap[charCodeA] - alphabetIndexMap[charCodeB];
+      }
+
+      return charCodeA - charCodeB;
+    }
+
+    ++indexA;
+    ++indexB;
+  }
+
+  if (indexA < lengthA) { // `b` is a substring of `a`
+    return 1;
+  }
+
+  if (indexB < lengthB) { // `a` is a substring of `b`
+    return -1;
+  }
+
+  return firstDifferenceInLeadingZeros;
+}
+
+const alphabetIndexMapCache = {};
+
+function buildAlphabetIndexMap(alphabet) {
+  const existingMap = alphabetIndexMapCache[alphabet];
+  if (existingMap !== undefined) {
+    return existingMap;
+  }
+
+  const indexMap = [];
+  const maxCharCode = alphabet.split('').reduce((maxCode, char) => {
+    return Math.max(maxCode, char.charCodeAt(0));
+  }, 0);
+
+  for (let i = 0; i <= maxCharCode; i++) {
+    indexMap.push(-1);
+  }
+
+  for (let i = 0; i < alphabet.length; i++) {
+    indexMap[alphabet.charCodeAt(i)] = i;
+  }
+
+  alphabetIndexMapCache[alphabet] = indexMap;
+
+  return indexMap;
+}
+
+module.exports = naturalCompare;
Index: frontend/node_modules/string-natural-compare/package.json
===================================================================
--- frontend/node_modules/string-natural-compare/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/string-natural-compare/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,54 @@
+{
+  "name": "string-natural-compare",
+  "version": "3.0.1",
+  "description": "Compare alphanumeric strings the same way a human would, using a natural order algorithm",
+  "author": "Nathan Woltman <nwoltman@outlook.com>",
+  "license": "MIT",
+  "main": "natural-compare.js",
+  "files": [
+    "natural-compare.js"
+  ],
+  "repository": "github:nwoltman/string-natural-compare",
+  "homepage": "https://github.com/nwoltman/string-natural-compare",
+  "bugs": "https://github.com/nwoltman/string-natural-compare/issues",
+  "keywords": [
+    "string",
+    "natural",
+    "compare",
+    "comparison",
+    "order",
+    "natcmp",
+    "strnatcmp",
+    "sort",
+    "natsort",
+    "alphanum",
+    "alphanumeric"
+  ],
+  "eslintIgnore": [
+    "benchmark/node_modules/",
+    "coverage/"
+  ],
+  "nyc": {
+    "reporter": [
+      "html",
+      "text-summary"
+    ],
+    "check-coverage": true,
+    "branches": 100,
+    "lines": 100,
+    "statements": 100
+  },
+  "devDependencies": {
+    "@nwoltman/eslint-config": "^0.6.0",
+    "coveralls": "^3.0.9",
+    "eslint": "^6.8.0",
+    "mocha": "^7.0.0",
+    "nyc": "^15.0.0",
+    "should": "^13.2.3"
+  },
+  "scripts": {
+    "lint": "eslint .",
+    "test": "eslint . && nyc mocha",
+    "coveralls": "nyc report --reporter=text-lcov | coveralls"
+  }
+}
