diff --git a/build/javascript/package-lock.json b/build/javascript/package-lock.json deleted file mode 100644 index 5f427b377..000000000 --- a/build/javascript/package-lock.json +++ /dev/null @@ -1,2218 +0,0 @@ -{ - "name": "owncast-dependencies", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@fortawesome/fontawesome-common-types": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", - "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==" - }, - "@fortawesome/fontawesome-svg-core": { - "version": "1.2.36", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", - "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - } - }, - "@fortawesome/free-regular-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz", - "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - } - }, - "@fortawesome/free-solid-svg-icons": { - "version": "5.15.4", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", - "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.36" - } - }, - "@fullhuman/postcss-purgecss": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz", - "integrity": "sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw==", - "requires": { - "postcss": "7.0.32", - "purgecss": "^2.3.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - } - } - }, - "@joeattardi/emoji-button": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/@joeattardi/emoji-button/-/emoji-button-4.6.4.tgz", - "integrity": "sha512-vXji10ZwgxRG6xGQ93SIBUQEltWYTs3do/FSEn3qrRWUuavrqIUhh1oMEPmhKARF0pokW6bNRCvVKI6wq//H6w==", - "requires": { - "@fortawesome/fontawesome-svg-core": "^1.2.28", - "@fortawesome/free-regular-svg-icons": "^5.13.0", - "@fortawesome/free-solid-svg-icons": "^5.13.0", - "@popperjs/core": "^2.4.0", - "escape-html": "^1.0.3", - "focus-trap": "^5.1.0", - "fuzzysort": "^1.1.4", - "tiny-emitter": "^2.1.0", - "tslib": "^2.0.0", - "twemoji": "^12.1.2" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true - }, - "@videojs/http-streaming": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.12.0.tgz", - "integrity": "sha512-vdQA0lDYBXGJqV2T02AGqg1w4dcgyRoN+bYG+G8uF4DpCEMhEtUI0BA4tRu4/Njar8w/9D5k0a1KX40pcvM3fA==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "3.0.4", - "aes-decrypter": "3.1.2", - "global": "^4.4.0", - "m3u8-parser": "4.7.0", - "mpd-parser": "0.19.2", - "mux.js": "5.14.1", - "video.js": "^6 || ^7" - }, - "dependencies": { - "@videojs/vhs-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz", - "integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==", - "requires": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0", - "url-toolkit": "^2.2.1" - } - } - } - }, - "@videojs/themes": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@videojs/themes/-/themes-1.0.1.tgz", - "integrity": "sha512-2b6YIIIz5x+/eSFdkSZ2RZJfHIMfP7bGODR3wDzLTqFF2kEKnJVIXxBUNzdZC/qiVETqAA2Ba6mCp+iXTUYt4A==", - "requires": { - "postcss-inline-svg": "^4.1.0" - } - }, - "@videojs/vhs-utils": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", - "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", - "requires": { - "@babel/runtime": "^7.12.5", - "global": "^4.4.0", - "url-toolkit": "^2.2.1" - } - }, - "@videojs/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==", - "requires": { - "@babel/runtime": "^7.5.5", - "global": "~4.4.0", - "is-function": "^1.0.1" - } - }, - "@xmldom/xmldom": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.9.tgz", - "integrity": "sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==" - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "aes-decrypter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz", - "integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.0", - "global": "^4.4.0", - "pkcs7": "^1.0.4" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "array-union": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", - "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", - "dev": true - }, - "autoprefixer": { - "version": "9.8.8", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.8.tgz", - "integrity": "sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA==", - "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "picocolors": "^0.2.1", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - } - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001434", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", - "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, - "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "css-declaration-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", - "integrity": "sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==", - "dev": true - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==" - }, - "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.0.tgz", - "integrity": "sha512-wWxave1wMlThGg4ueK98jFKaNqXnQd1nVZpSkQ9XvR+YymlzP1ofWqES1JkHtI250LksP9z5JH+oDcrKDJezAg==", - "dev": true, - "requires": { - "cssnano-preset-default": "^5.2.0", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.2.13", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.13.tgz", - "integrity": "sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.3", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.1", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - } - }, - "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "requires": { - "css-tree": "^1.1.2" - } - }, - "defined": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", - "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==" - }, - "dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true - }, - "detective": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", - "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", - "requires": { - "acorn-node": "^1.8.2", - "defined": "^1.0.0", - "minimist": "^1.2.6" - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "electron-to-chromium": { - "version": "1.4.284", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", - "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "focus-trap": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz", - "integrity": "sha512-CkB/nrO55069QAUjWFBpX6oc+9V90Qhgpe6fBWApzruMq5gnlh90Oo7iSSDK7pKiV5ugG6OY2AXM5mxcmL3lwQ==", - "requires": { - "tabbable": "^4.0.0", - "xtend": "^4.0.1" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "fuzzysort": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.9.0.tgz", - "integrity": "sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-stdin": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", - "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "globby": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", - "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", - "dev": true, - "requires": { - "array-union": "^3.0.1", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.7", - "ignore": "^5.1.9", - "merge2": "^1.4.1", - "slash": "^4.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "htm": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", - "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" - }, - "html-tags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", - "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==" - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", - "dev": true - }, - "individual": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", - "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "jsonfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", - "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^0.1.2" - } - }, - "keycode": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz", - "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==" - }, - "lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "dev": true - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "m3u8-parser": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz", - "integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.0", - "global": "^4.4.0" - } - }, - "mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "micromodal": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.10.tgz", - "integrity": "sha512-BUrEnzMPFBwK8nOE4xUDYHLrlGlLULQVjpja99tpJQPSUEWgw3kTLp1n1qv0HmKU29AiHE7Y7sMLiRziDK4ghQ==" - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" - }, - "mpd-parser": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.19.2.tgz", - "integrity": "sha512-M5tAIdtBM2TN+OSTz/37T7V+h9ZLvhyNqq4TNIdtjAQ/Hg8UnMRf5nJQDjffcXag3POXi31yUJQEKOXdcAM/nw==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.2", - "@xmldom/xmldom": "^0.7.2", - "global": "^4.4.0" - } - }, - "mux.js": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.14.1.tgz", - "integrity": "sha512-38kA/xjWRDzMbcpHQfhKbJAME8eTZVsb9U2Puk890oGvGqnyu8B/AkKdICKPHkigfqYX9MY20vje88TP14nhog==", - "requires": { - "@babel/runtime": "^7.11.2" - } - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true - }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "requires": { - "lodash": "^4.17.21" - } - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "normalize.css": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", - "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pkcs7": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", - "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", - "requires": { - "@babel/runtime": "^7.5.5" - } - }, - "postcss": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz", - "integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==", - "dev": true, - "requires": { - "nanoid": "^3.3.1", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "dependencies": { - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - } - } - }, - "postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-cli": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.1.0.tgz", - "integrity": "sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==", - "dev": true, - "requires": { - "chokidar": "^3.3.0", - "dependency-graph": "^0.11.0", - "fs-extra": "^10.0.0", - "get-stdin": "^9.0.0", - "globby": "^12.0.0", - "picocolors": "^1.0.0", - "postcss-load-config": "^3.0.0", - "postcss-reporter": "^7.0.0", - "pretty-hrtime": "^1.0.3", - "read-cache": "^1.0.0", - "slash": "^4.0.0", - "yargs": "^17.0.0" - }, - "dependencies": { - "fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true - }, - "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true - }, - "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true - }, - "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true - }, - "postcss-functions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-functions/-/postcss-functions-3.0.0.tgz", - "integrity": "sha512-N5yWXWKA+uhpLQ9ZhBRl2bIAdM6oVJYpDojuI1nF2SzXBimJcdjFwiAouBVbO5VuOF3qA6BSFWFc3wXbbj72XQ==", - "requires": { - "glob": "^7.1.2", - "object-assign": "^4.1.1", - "postcss": "^6.0.9", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-inline-svg": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-inline-svg/-/postcss-inline-svg-4.1.0.tgz", - "integrity": "sha512-0pYBJyoQ9/sJViYRc1cNOOTM7DYh0/rmASB0TBeRmWkG8YFK2tmgdkfjHkbRma1iFtBFKFHZFsHwRTDZTMKzSQ==", - "requires": { - "css-select": "^2.0.2", - "dom-serializer": "^0.1.1", - "htmlparser2": "^3.10.1", - "postcss": "^7.0.17", - "postcss-value-parser": "^4.0.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - } - } - }, - "postcss-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-2.0.3.tgz", - "integrity": "sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w==", - "requires": { - "camelcase-css": "^2.0.1", - "postcss": "^7.0.18" - }, - "dependencies": { - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - } - } - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - } - }, - "postcss-merge-rules": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.3.tgz", - "integrity": "sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dev": true, - "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-nested": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.2.3.tgz", - "integrity": "sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw==", - "requires": { - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - } - } - }, - "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true - }, - "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dev": true, - "requires": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dev": true, - "requires": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-reduce-initial": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.1.tgz", - "integrity": "sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-reporter": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz", - "integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==", - "dev": true, - "requires": { - "picocolors": "^1.0.0", - "thenby": "^1.3.4" - }, - "dependencies": { - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - } - } - }, - "postcss-selector-parser": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz", - "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==", - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - } - }, - "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "preact": { - "version": "10.6.6", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.6.6.tgz", - "integrity": "sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw==" - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "purgecss": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-2.3.0.tgz", - "integrity": "sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ==", - "requires": { - "commander": "^5.0.0", - "glob": "^7.0.0", - "postcss": "7.0.32", - "postcss-selector-parser": "^6.0.2" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - } - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "reduce-css-calc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", - "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - } - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rust-result": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", - "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", - "requires": { - "individual": "^2.0.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-json-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", - "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", - "requires": { - "rust-result": "^1.0.0" - } - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "requires": { - "is-arrayish": "^0.3.1" - } - }, - "slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "dev": true, - "requires": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - } - } - }, - "tabbable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz", - "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==" - }, - "tailwindcss": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.9.6.tgz", - "integrity": "sha512-nY8WYM/RLPqGsPEGEV2z63riyQPcHYZUJpAwdyBzVpxQHOHqHE+F/fvbCeXhdF1+TA5l72vSkZrtYCB9hRcwkQ==", - "requires": { - "@fullhuman/postcss-purgecss": "^2.1.2", - "autoprefixer": "^9.4.5", - "browserslist": "^4.12.0", - "bytes": "^3.0.0", - "chalk": "^3.0.0 || ^4.0.0", - "color": "^3.1.2", - "detective": "^5.2.0", - "fs-extra": "^8.0.0", - "html-tags": "^3.1.0", - "lodash": "^4.17.20", - "node-emoji": "^1.8.1", - "normalize.css": "^8.0.1", - "object-hash": "^2.0.3", - "postcss": "^7.0.11", - "postcss-functions": "^3.0.0", - "postcss-js": "^2.0.0", - "postcss-nested": "^4.1.1", - "postcss-selector-parser": "^6.0.0", - "postcss-value-parser": "^4.1.0", - "pretty-hrtime": "^1.0.3", - "reduce-css-calc": "^2.1.6", - "resolve": "^1.14.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "requires": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - } - } - } - }, - "thenby": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", - "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", - "dev": true - }, - "tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" - }, - "twemoji": { - "version": "12.1.6", - "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-12.1.6.tgz", - "integrity": "sha512-FIKi9Jne5IiDGDWekoANJ1a8ltUKVbJLEIR8XUpbFRDMqIPgLWnYgjeWZ1KOrdiTztRCAa9x4v+5w5OuiJOGVw==", - "requires": { - "fs-extra": "^8.0.1", - "jsonfile": "^5.0.0", - "twemoji-parser": "12.1.3", - "universalify": "^0.1.2" - } - }, - "twemoji-parser": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-12.1.3.tgz", - "integrity": "sha512-ND4LZXF4X92/PFrzSgGkq6KPPg8swy/U0yRw1k/+izWRVmq1HYi3khPwV3XIB6FRudgVICAaBhJfW8e8G3HC7Q==" - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "dependencies": { - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - } - } - }, - "url-toolkit": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", - "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "video.js": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.17.0.tgz", - "integrity": "sha512-8RbLu9+Pdpep9OTPncUHIvZXFgn/7hKdPnSTE/lGSnlFSucXtTUBp41R7NDwncscMLQ0WgazUbmFlvr4MNWMbA==", - "requires": { - "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.12.0", - "@videojs/vhs-utils": "^3.0.3", - "@videojs/xhr": "2.6.0", - "aes-decrypter": "3.1.2", - "global": "^4.4.0", - "keycode": "^2.2.0", - "m3u8-parser": "4.7.0", - "mpd-parser": "0.19.2", - "mux.js": "5.14.1", - "safe-json-parse": "4.0.0", - "videojs-font": "3.2.0", - "videojs-vtt.js": "^0.15.3" - } - }, - "videojs-font": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", - "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==" - }, - "videojs-vtt.js": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz", - "integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==", - "requires": { - "global": "^4.3.1" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true - }, - "yargs": { - "version": "17.6.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", - "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } -} diff --git a/webroot/img/authenticated.svg b/webroot/img/authenticated.svg deleted file mode 100644 index 8437cd117..000000000 --- a/webroot/img/authenticated.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/webroot/img/ban-user-grey.svg b/webroot/img/ban-user-grey.svg deleted file mode 100644 index 6cdca98fd..000000000 --- a/webroot/img/ban-user-grey.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/ban-user.svg b/webroot/img/ban-user.svg deleted file mode 100644 index dd5079d39..000000000 --- a/webroot/img/ban-user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/bot.svg b/webroot/img/bot.svg deleted file mode 100644 index a39c87f79..000000000 --- a/webroot/img/bot.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/browser-push-notifications-settings.png b/webroot/img/browser-push-notifications-settings.png deleted file mode 100644 index 83338bb40..000000000 Binary files a/webroot/img/browser-push-notifications-settings.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-gg.png b/webroot/img/emoji/Reaper-gg.png deleted file mode 100644 index de6d571e6..000000000 Binary files a/webroot/img/emoji/Reaper-gg.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-hi.png b/webroot/img/emoji/Reaper-hi.png deleted file mode 100644 index dc6b6aedc..000000000 Binary files a/webroot/img/emoji/Reaper-hi.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-hype.png b/webroot/img/emoji/Reaper-hype.png deleted file mode 100644 index 2138dba90..000000000 Binary files a/webroot/img/emoji/Reaper-hype.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-lol.png b/webroot/img/emoji/Reaper-lol.png deleted file mode 100644 index 8c4eff264..000000000 Binary files a/webroot/img/emoji/Reaper-lol.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-love.png b/webroot/img/emoji/Reaper-love.png deleted file mode 100644 index 5e0f3ba7b..000000000 Binary files a/webroot/img/emoji/Reaper-love.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-rage.png b/webroot/img/emoji/Reaper-rage.png deleted file mode 100644 index 3da0a5d83..000000000 Binary files a/webroot/img/emoji/Reaper-rage.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-rip.png b/webroot/img/emoji/Reaper-rip.png deleted file mode 100644 index 89c13c789..000000000 Binary files a/webroot/img/emoji/Reaper-rip.png and /dev/null differ diff --git a/webroot/img/emoji/Reaper-wtf.png b/webroot/img/emoji/Reaper-wtf.png deleted file mode 100644 index e21adb6a2..000000000 Binary files a/webroot/img/emoji/Reaper-wtf.png and /dev/null differ diff --git a/webroot/img/emoji/ac-box.png b/webroot/img/emoji/ac-box.png deleted file mode 100644 index 18bd9b35d..000000000 Binary files a/webroot/img/emoji/ac-box.png and /dev/null differ diff --git a/webroot/img/emoji/ac-construction.png b/webroot/img/emoji/ac-construction.png deleted file mode 100644 index ee2b6f3c3..000000000 Binary files a/webroot/img/emoji/ac-construction.png and /dev/null differ diff --git a/webroot/img/emoji/ac-fossil.png b/webroot/img/emoji/ac-fossil.png deleted file mode 100644 index 16d4fad73..000000000 Binary files a/webroot/img/emoji/ac-fossil.png and /dev/null differ diff --git a/webroot/img/emoji/ac-item-leaf.png b/webroot/img/emoji/ac-item-leaf.png deleted file mode 100644 index 6abede7b1..000000000 Binary files a/webroot/img/emoji/ac-item-leaf.png and /dev/null differ diff --git a/webroot/img/emoji/ac-kkslider.png b/webroot/img/emoji/ac-kkslider.png deleted file mode 100644 index 309d768f5..000000000 Binary files a/webroot/img/emoji/ac-kkslider.png and /dev/null differ diff --git a/webroot/img/emoji/ac-moneytree.png b/webroot/img/emoji/ac-moneytree.png deleted file mode 100644 index 3e370acd4..000000000 Binary files a/webroot/img/emoji/ac-moneytree.png and /dev/null differ diff --git a/webroot/img/emoji/ac-mosquito.png b/webroot/img/emoji/ac-mosquito.png deleted file mode 100644 index 50ddb4110..000000000 Binary files a/webroot/img/emoji/ac-mosquito.png and /dev/null differ diff --git a/webroot/img/emoji/ac-shirt.png b/webroot/img/emoji/ac-shirt.png deleted file mode 100644 index 41ab1d8de..000000000 Binary files a/webroot/img/emoji/ac-shirt.png and /dev/null differ diff --git a/webroot/img/emoji/ac-song.png b/webroot/img/emoji/ac-song.png deleted file mode 100644 index dfe9d847c..000000000 Binary files a/webroot/img/emoji/ac-song.png and /dev/null differ diff --git a/webroot/img/emoji/ac-tree.png b/webroot/img/emoji/ac-tree.png deleted file mode 100644 index 499ee4ec4..000000000 Binary files a/webroot/img/emoji/ac-tree.png and /dev/null differ diff --git a/webroot/img/emoji/ac-turnip.png b/webroot/img/emoji/ac-turnip.png deleted file mode 100644 index 893ef9cff..000000000 Binary files a/webroot/img/emoji/ac-turnip.png and /dev/null differ diff --git a/webroot/img/emoji/ac-weeds.png b/webroot/img/emoji/ac-weeds.png deleted file mode 100644 index 6d3b23716..000000000 Binary files a/webroot/img/emoji/ac-weeds.png and /dev/null differ diff --git a/webroot/img/emoji/alert.gif b/webroot/img/emoji/alert.gif deleted file mode 100644 index 43b4b981e..000000000 Binary files a/webroot/img/emoji/alert.gif and /dev/null differ diff --git a/webroot/img/emoji/bananadance.gif b/webroot/img/emoji/bananadance.gif deleted file mode 100644 index da6f470bc..000000000 Binary files a/webroot/img/emoji/bananadance.gif and /dev/null differ diff --git a/webroot/img/emoji/bb8.png b/webroot/img/emoji/bb8.png deleted file mode 100644 index e97e3f3d5..000000000 Binary files a/webroot/img/emoji/bb8.png and /dev/null differ diff --git a/webroot/img/emoji/beerparrot.gif b/webroot/img/emoji/beerparrot.gif deleted file mode 100644 index 2cd83ee15..000000000 Binary files a/webroot/img/emoji/beerparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/bells.png b/webroot/img/emoji/bells.png deleted file mode 100644 index 1a20ab040..000000000 Binary files a/webroot/img/emoji/bells.png and /dev/null differ diff --git a/webroot/img/emoji/birthdaypartyparrot.gif b/webroot/img/emoji/birthdaypartyparrot.gif deleted file mode 100644 index 4c166eabb..000000000 Binary files a/webroot/img/emoji/birthdaypartyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/blacklightsaber.png b/webroot/img/emoji/blacklightsaber.png deleted file mode 100644 index 1a537968a..000000000 Binary files a/webroot/img/emoji/blacklightsaber.png and /dev/null differ diff --git a/webroot/img/emoji/bluelightsaber.png b/webroot/img/emoji/bluelightsaber.png deleted file mode 100644 index b509c73ea..000000000 Binary files a/webroot/img/emoji/bluelightsaber.png and /dev/null differ diff --git a/webroot/img/emoji/bluntparrot.gif b/webroot/img/emoji/bluntparrot.gif deleted file mode 100644 index 6d33f7d38..000000000 Binary files a/webroot/img/emoji/bluntparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/bobaparrot.gif b/webroot/img/emoji/bobaparrot.gif deleted file mode 100644 index 17ecdfad9..000000000 Binary files a/webroot/img/emoji/bobaparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/cakeparrot.gif b/webroot/img/emoji/cakeparrot.gif deleted file mode 100644 index 8ddb47f04..000000000 Binary files a/webroot/img/emoji/cakeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/chewbacca.png b/webroot/img/emoji/chewbacca.png deleted file mode 100644 index 4cae67bbe..000000000 Binary files a/webroot/img/emoji/chewbacca.png and /dev/null differ diff --git a/webroot/img/emoji/chillparrot.gif b/webroot/img/emoji/chillparrot.gif deleted file mode 100644 index 0d7a560e1..000000000 Binary files a/webroot/img/emoji/chillparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/christmasparrot.gif b/webroot/img/emoji/christmasparrot.gif deleted file mode 100644 index 417c3e074..000000000 Binary files a/webroot/img/emoji/christmasparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/coffeeparrot.gif b/webroot/img/emoji/coffeeparrot.gif deleted file mode 100644 index 3a935d229..000000000 Binary files a/webroot/img/emoji/coffeeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/confusedparrot.gif b/webroot/img/emoji/confusedparrot.gif deleted file mode 100644 index e641f4393..000000000 Binary files a/webroot/img/emoji/confusedparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/copparrot.gif b/webroot/img/emoji/copparrot.gif deleted file mode 100644 index b41a4744d..000000000 Binary files a/webroot/img/emoji/copparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/coronavirus.png b/webroot/img/emoji/coronavirus.png deleted file mode 100644 index 6c6566485..000000000 Binary files a/webroot/img/emoji/coronavirus.png and /dev/null differ diff --git a/webroot/img/emoji/covid19parrot.gif b/webroot/img/emoji/covid19parrot.gif deleted file mode 100644 index 70beb621b..000000000 Binary files a/webroot/img/emoji/covid19parrot.gif and /dev/null differ diff --git a/webroot/img/emoji/cryptoparrot.gif b/webroot/img/emoji/cryptoparrot.gif deleted file mode 100644 index acd98d0db..000000000 Binary files a/webroot/img/emoji/cryptoparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/dabparrot.gif b/webroot/img/emoji/dabparrot.gif deleted file mode 100644 index 5f95dd4a2..000000000 Binary files a/webroot/img/emoji/dabparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/dadparrot.gif b/webroot/img/emoji/dadparrot.gif deleted file mode 100644 index 5e025ddc5..000000000 Binary files a/webroot/img/emoji/dadparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/daftpunkparrot.gif b/webroot/img/emoji/daftpunkparrot.gif deleted file mode 100644 index be7a1cf31..000000000 Binary files a/webroot/img/emoji/daftpunkparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/darkbeerparrot.gif b/webroot/img/emoji/darkbeerparrot.gif deleted file mode 100644 index c37edb504..000000000 Binary files a/webroot/img/emoji/darkbeerparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/darkmodeparrot.gif b/webroot/img/emoji/darkmodeparrot.gif deleted file mode 100644 index b9ed2051d..000000000 Binary files a/webroot/img/emoji/darkmodeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/darth_vader.png b/webroot/img/emoji/darth_vader.png deleted file mode 100644 index 44f43d03d..000000000 Binary files a/webroot/img/emoji/darth_vader.png and /dev/null differ diff --git a/webroot/img/emoji/dealwithitparrot.gif b/webroot/img/emoji/dealwithitparrot.gif deleted file mode 100644 index f501e5ea5..000000000 Binary files a/webroot/img/emoji/dealwithitparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/death_star.png b/webroot/img/emoji/death_star.png deleted file mode 100644 index 003a28a56..000000000 Binary files a/webroot/img/emoji/death_star.png and /dev/null differ diff --git a/webroot/img/emoji/discoparrot.gif b/webroot/img/emoji/discoparrot.gif deleted file mode 100644 index 47a754842..000000000 Binary files a/webroot/img/emoji/discoparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/division-gg.png b/webroot/img/emoji/division-gg.png deleted file mode 100644 index d704b46d4..000000000 Binary files a/webroot/img/emoji/division-gg.png and /dev/null differ diff --git a/webroot/img/emoji/division-hi.png b/webroot/img/emoji/division-hi.png deleted file mode 100644 index 437b36850..000000000 Binary files a/webroot/img/emoji/division-hi.png and /dev/null differ diff --git a/webroot/img/emoji/division-hype.png b/webroot/img/emoji/division-hype.png deleted file mode 100644 index b6260bc95..000000000 Binary files a/webroot/img/emoji/division-hype.png and /dev/null differ diff --git a/webroot/img/emoji/division-lol.png b/webroot/img/emoji/division-lol.png deleted file mode 100644 index d085493f4..000000000 Binary files a/webroot/img/emoji/division-lol.png and /dev/null differ diff --git a/webroot/img/emoji/division-omg.png b/webroot/img/emoji/division-omg.png deleted file mode 100644 index b1100cfc9..000000000 Binary files a/webroot/img/emoji/division-omg.png and /dev/null differ diff --git a/webroot/img/emoji/division-rage.png b/webroot/img/emoji/division-rage.png deleted file mode 100644 index 55d13a3e7..000000000 Binary files a/webroot/img/emoji/division-rage.png and /dev/null differ diff --git a/webroot/img/emoji/division-rip.png b/webroot/img/emoji/division-rip.png deleted file mode 100644 index f57c78b4a..000000000 Binary files a/webroot/img/emoji/division-rip.png and /dev/null differ diff --git a/webroot/img/emoji/division-wtf.png b/webroot/img/emoji/division-wtf.png deleted file mode 100644 index 654cde903..000000000 Binary files a/webroot/img/emoji/division-wtf.png and /dev/null differ diff --git a/webroot/img/emoji/docparrot.gif b/webroot/img/emoji/docparrot.gif deleted file mode 100644 index 2fb502de5..000000000 Binary files a/webroot/img/emoji/docparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/donutparrot.gif b/webroot/img/emoji/donutparrot.gif deleted file mode 100644 index 9137e9066..000000000 Binary files a/webroot/img/emoji/donutparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/doom_mad.gif b/webroot/img/emoji/doom_mad.gif deleted file mode 100644 index 1f124b810..000000000 Binary files a/webroot/img/emoji/doom_mad.gif and /dev/null differ diff --git a/webroot/img/emoji/empire.png b/webroot/img/emoji/empire.png deleted file mode 100644 index da2fb8c95..000000000 Binary files a/webroot/img/emoji/empire.png and /dev/null differ diff --git a/webroot/img/emoji/everythingsfineparrot.gif b/webroot/img/emoji/everythingsfineparrot.gif deleted file mode 100644 index 5680c322e..000000000 Binary files a/webroot/img/emoji/everythingsfineparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/evilparrot.gif b/webroot/img/emoji/evilparrot.gif deleted file mode 100644 index edb93c02d..000000000 Binary files a/webroot/img/emoji/evilparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/explodyparrot.gif b/webroot/img/emoji/explodyparrot.gif deleted file mode 100644 index 387005d36..000000000 Binary files a/webroot/img/emoji/explodyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/fixparrot.gif b/webroot/img/emoji/fixparrot.gif deleted file mode 100644 index 655d423a8..000000000 Binary files a/webroot/img/emoji/fixparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/flyingmoneyparrot.gif b/webroot/img/emoji/flyingmoneyparrot.gif deleted file mode 100644 index abf389dd5..000000000 Binary files a/webroot/img/emoji/flyingmoneyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/footballparrot.gif b/webroot/img/emoji/footballparrot.gif deleted file mode 100644 index 49472b74a..000000000 Binary files a/webroot/img/emoji/footballparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/gabe1.png b/webroot/img/emoji/gabe1.png deleted file mode 100644 index 89b8c4e5e..000000000 Binary files a/webroot/img/emoji/gabe1.png and /dev/null differ diff --git a/webroot/img/emoji/gabe2.png b/webroot/img/emoji/gabe2.png deleted file mode 100644 index 987d91cf3..000000000 Binary files a/webroot/img/emoji/gabe2.png and /dev/null differ diff --git a/webroot/img/emoji/gentlemanparrot.gif b/webroot/img/emoji/gentlemanparrot.gif deleted file mode 100644 index 8e8f05bb7..000000000 Binary files a/webroot/img/emoji/gentlemanparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/githubparrot.gif b/webroot/img/emoji/githubparrot.gif deleted file mode 100644 index 1d7bd14ad..000000000 Binary files a/webroot/img/emoji/githubparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/goomba.gif b/webroot/img/emoji/goomba.gif deleted file mode 100644 index c6bb719e7..000000000 Binary files a/webroot/img/emoji/goomba.gif and /dev/null differ diff --git a/webroot/img/emoji/gothparrot.gif b/webroot/img/emoji/gothparrot.gif deleted file mode 100644 index 361b68ecf..000000000 Binary files a/webroot/img/emoji/gothparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/hamburgerparrot.gif b/webroot/img/emoji/hamburgerparrot.gif deleted file mode 100644 index 192b0ff17..000000000 Binary files a/webroot/img/emoji/hamburgerparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/harrypotterparrot.gif b/webroot/img/emoji/harrypotterparrot.gif deleted file mode 100644 index 032a37e91..000000000 Binary files a/webroot/img/emoji/harrypotterparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/headbangingparrot.gif b/webroot/img/emoji/headbangingparrot.gif deleted file mode 100644 index 9aad2ecb6..000000000 Binary files a/webroot/img/emoji/headbangingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/headingparrot.gif b/webroot/img/emoji/headingparrot.gif deleted file mode 100644 index b17002c55..000000000 Binary files a/webroot/img/emoji/headingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/headsetparrot.gif b/webroot/img/emoji/headsetparrot.gif deleted file mode 100644 index 516a04f1f..000000000 Binary files a/webroot/img/emoji/headsetparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/hmmparrot.gif b/webroot/img/emoji/hmmparrot.gif deleted file mode 100644 index 223a6c251..000000000 Binary files a/webroot/img/emoji/hmmparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/hypnoparrot.gif b/webroot/img/emoji/hypnoparrot.gif deleted file mode 100644 index a6a0983d0..000000000 Binary files a/webroot/img/emoji/hypnoparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/icecreamparrot.gif b/webroot/img/emoji/icecreamparrot.gif deleted file mode 100644 index 0a5093fd0..000000000 Binary files a/webroot/img/emoji/icecreamparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/illuminatiparrot.gif b/webroot/img/emoji/illuminatiparrot.gif deleted file mode 100644 index a0c4e7981..000000000 Binary files a/webroot/img/emoji/illuminatiparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/jediparrot.gif b/webroot/img/emoji/jediparrot.gif deleted file mode 100644 index 410ca698b..000000000 Binary files a/webroot/img/emoji/jediparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/keanu_thanks.gif b/webroot/img/emoji/keanu_thanks.gif deleted file mode 100644 index 989b9b759..000000000 Binary files a/webroot/img/emoji/keanu_thanks.gif and /dev/null differ diff --git a/webroot/img/emoji/laptop_parrot.gif b/webroot/img/emoji/laptop_parrot.gif deleted file mode 100644 index b14bb1846..000000000 Binary files a/webroot/img/emoji/laptop_parrot.gif and /dev/null differ diff --git a/webroot/img/emoji/loveparrot.gif b/webroot/img/emoji/loveparrot.gif deleted file mode 100644 index c0d14edca..000000000 Binary files a/webroot/img/emoji/loveparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/mandalorian.png b/webroot/img/emoji/mandalorian.png deleted file mode 100644 index 7db4a3b8e..000000000 Binary files a/webroot/img/emoji/mandalorian.png and /dev/null differ diff --git a/webroot/img/emoji/margaritaparrot.gif b/webroot/img/emoji/margaritaparrot.gif deleted file mode 100644 index 10a8c6a83..000000000 Binary files a/webroot/img/emoji/margaritaparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/mario.gif b/webroot/img/emoji/mario.gif deleted file mode 100644 index 87f113333..000000000 Binary files a/webroot/img/emoji/mario.gif and /dev/null differ diff --git a/webroot/img/emoji/matrixparrot.gif b/webroot/img/emoji/matrixparrot.gif deleted file mode 100644 index 7281a8435..000000000 Binary files a/webroot/img/emoji/matrixparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/meldparrot.gif b/webroot/img/emoji/meldparrot.gif deleted file mode 100644 index ae4605336..000000000 Binary files a/webroot/img/emoji/meldparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/metalparrot.gif b/webroot/img/emoji/metalparrot.gif deleted file mode 100644 index f358b2fff..000000000 Binary files a/webroot/img/emoji/metalparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/michaeljacksonparrot.gif b/webroot/img/emoji/michaeljacksonparrot.gif deleted file mode 100644 index cfa840500..000000000 Binary files a/webroot/img/emoji/michaeljacksonparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/moonparrot.gif b/webroot/img/emoji/moonparrot.gif deleted file mode 100644 index 098938ab2..000000000 Binary files a/webroot/img/emoji/moonparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/moonwalkingparrot.gif b/webroot/img/emoji/moonwalkingparrot.gif deleted file mode 100644 index 873f828f5..000000000 Binary files a/webroot/img/emoji/moonwalkingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/mustacheparrot.gif b/webroot/img/emoji/mustacheparrot.gif deleted file mode 100644 index e71fe28d8..000000000 Binary files a/webroot/img/emoji/mustacheparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/nicolas_cage_party.gif b/webroot/img/emoji/nicolas_cage_party.gif deleted file mode 100644 index 541431710..000000000 Binary files a/webroot/img/emoji/nicolas_cage_party.gif and /dev/null differ diff --git a/webroot/img/emoji/nodeparrot.gif b/webroot/img/emoji/nodeparrot.gif deleted file mode 100644 index 6735b0b30..000000000 Binary files a/webroot/img/emoji/nodeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/norwegianblueparrot.gif b/webroot/img/emoji/norwegianblueparrot.gif deleted file mode 100644 index 0aa9583ec..000000000 Binary files a/webroot/img/emoji/norwegianblueparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/opensourceparrot.gif b/webroot/img/emoji/opensourceparrot.gif deleted file mode 100644 index 706774342..000000000 Binary files a/webroot/img/emoji/opensourceparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/originalparrot.gif b/webroot/img/emoji/originalparrot.gif deleted file mode 100644 index 428cc2239..000000000 Binary files a/webroot/img/emoji/originalparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/owncast.png b/webroot/img/emoji/owncast.png deleted file mode 100644 index 6e7fdc9b3..000000000 Binary files a/webroot/img/emoji/owncast.png and /dev/null differ diff --git a/webroot/img/emoji/palpatine.png b/webroot/img/emoji/palpatine.png deleted file mode 100644 index ca10fe673..000000000 Binary files a/webroot/img/emoji/palpatine.png and /dev/null differ diff --git a/webroot/img/emoji/papalparrot.gif b/webroot/img/emoji/papalparrot.gif deleted file mode 100644 index 16ec6ba25..000000000 Binary files a/webroot/img/emoji/papalparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/parrot.gif b/webroot/img/emoji/parrot.gif deleted file mode 100644 index b8c261a01..000000000 Binary files a/webroot/img/emoji/parrot.gif and /dev/null differ diff --git a/webroot/img/emoji/parrotnotfound.gif b/webroot/img/emoji/parrotnotfound.gif deleted file mode 100644 index a039df9fc..000000000 Binary files a/webroot/img/emoji/parrotnotfound.gif and /dev/null differ diff --git a/webroot/img/emoji/partyparrot.gif b/webroot/img/emoji/partyparrot.gif deleted file mode 100644 index b88ecc41f..000000000 Binary files a/webroot/img/emoji/partyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/phparrot.gif b/webroot/img/emoji/phparrot.gif deleted file mode 100644 index 95e33b4f2..000000000 Binary files a/webroot/img/emoji/phparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/pirateparrot.gif b/webroot/img/emoji/pirateparrot.gif deleted file mode 100644 index 028848abc..000000000 Binary files a/webroot/img/emoji/pirateparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/pizzaparrot.gif b/webroot/img/emoji/pizzaparrot.gif deleted file mode 100644 index 56d9dfc29..000000000 Binary files a/webroot/img/emoji/pizzaparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/pokeparrot.gif b/webroot/img/emoji/pokeparrot.gif deleted file mode 100644 index a9adc861a..000000000 Binary files a/webroot/img/emoji/pokeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/popcornparrot.gif b/webroot/img/emoji/popcornparrot.gif deleted file mode 100644 index 65b8585af..000000000 Binary files a/webroot/img/emoji/popcornparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/porg.png b/webroot/img/emoji/porg.png deleted file mode 100644 index 68d08bfa1..000000000 Binary files a/webroot/img/emoji/porg.png and /dev/null differ diff --git a/webroot/img/emoji/portalparrot.gif b/webroot/img/emoji/portalparrot.gif deleted file mode 100644 index 5971fbd2a..000000000 Binary files a/webroot/img/emoji/portalparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/pumpkinparrot.gif b/webroot/img/emoji/pumpkinparrot.gif deleted file mode 100644 index f453ce26f..000000000 Binary files a/webroot/img/emoji/pumpkinparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/quadparrot.gif b/webroot/img/emoji/quadparrot.gif deleted file mode 100644 index 9f1e3195b..000000000 Binary files a/webroot/img/emoji/quadparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/r2d2.png b/webroot/img/emoji/r2d2.png deleted file mode 100644 index 0a7fa098f..000000000 Binary files a/webroot/img/emoji/r2d2.png and /dev/null differ diff --git a/webroot/img/emoji/redenvelopeparrot.gif b/webroot/img/emoji/redenvelopeparrot.gif deleted file mode 100644 index b40c76cf6..000000000 Binary files a/webroot/img/emoji/redenvelopeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/ripparrot.gif b/webroot/img/emoji/ripparrot.gif deleted file mode 100644 index 164250ecd..000000000 Binary files a/webroot/img/emoji/ripparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/rotatingparrot.gif b/webroot/img/emoji/rotatingparrot.gif deleted file mode 100644 index 1916cd0be..000000000 Binary files a/webroot/img/emoji/rotatingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/ryangoslingparrot.gif b/webroot/img/emoji/ryangoslingparrot.gif deleted file mode 100644 index f37d170c4..000000000 Binary files a/webroot/img/emoji/ryangoslingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/rythmicalparrot.gif b/webroot/img/emoji/rythmicalparrot.gif deleted file mode 100644 index db3e52d62..000000000 Binary files a/webroot/img/emoji/rythmicalparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/sadparrot.gif b/webroot/img/emoji/sadparrot.gif deleted file mode 100644 index 25b500dd0..000000000 Binary files a/webroot/img/emoji/sadparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/schnitzelparrot.gif b/webroot/img/emoji/schnitzelparrot.gif deleted file mode 100644 index 00d22d7ab..000000000 Binary files a/webroot/img/emoji/schnitzelparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/scienceparrot.gif b/webroot/img/emoji/scienceparrot.gif deleted file mode 100644 index b5e434250..000000000 Binary files a/webroot/img/emoji/scienceparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/shipitparrot.gif b/webroot/img/emoji/shipitparrot.gif deleted file mode 100644 index 8d1ed30f7..000000000 Binary files a/webroot/img/emoji/shipitparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/shufflepartyparrot.gif b/webroot/img/emoji/shufflepartyparrot.gif deleted file mode 100644 index 7e754cbd0..000000000 Binary files a/webroot/img/emoji/shufflepartyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/sintparrot.gif b/webroot/img/emoji/sintparrot.gif deleted file mode 100644 index 26f083bba..000000000 Binary files a/webroot/img/emoji/sintparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/sithparrot.gif b/webroot/img/emoji/sithparrot.gif deleted file mode 100644 index b039e67f3..000000000 Binary files a/webroot/img/emoji/sithparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/skiparrot.gif b/webroot/img/emoji/skiparrot.gif deleted file mode 100644 index 7cfa3c00c..000000000 Binary files a/webroot/img/emoji/skiparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/sleepingparrot.gif b/webroot/img/emoji/sleepingparrot.gif deleted file mode 100644 index 6f0b6dde4..000000000 Binary files a/webroot/img/emoji/sleepingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/sonic.gif b/webroot/img/emoji/sonic.gif deleted file mode 100644 index e9a9f4bac..000000000 Binary files a/webroot/img/emoji/sonic.gif and /dev/null differ diff --git a/webroot/img/emoji/spyparrot.gif b/webroot/img/emoji/spyparrot.gif deleted file mode 100644 index 031b64e04..000000000 Binary files a/webroot/img/emoji/spyparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/stalkerparrot.gif b/webroot/img/emoji/stalkerparrot.gif deleted file mode 100644 index 349d6494b..000000000 Binary files a/webroot/img/emoji/stalkerparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/starwars.png b/webroot/img/emoji/starwars.png deleted file mode 100644 index cef654682..000000000 Binary files a/webroot/img/emoji/starwars.png and /dev/null differ diff --git a/webroot/img/emoji/stayhomeparrot.gif b/webroot/img/emoji/stayhomeparrot.gif deleted file mode 100644 index 800adafd8..000000000 Binary files a/webroot/img/emoji/stayhomeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/storm_trooper.gif b/webroot/img/emoji/storm_trooper.gif deleted file mode 100644 index 0f66de7ad..000000000 Binary files a/webroot/img/emoji/storm_trooper.gif and /dev/null differ diff --git a/webroot/img/emoji/stormtrooper.png b/webroot/img/emoji/stormtrooper.png deleted file mode 100644 index ae9535864..000000000 Binary files a/webroot/img/emoji/stormtrooper.png and /dev/null differ diff --git a/webroot/img/emoji/sushiparrot.gif b/webroot/img/emoji/sushiparrot.gif deleted file mode 100644 index 27220188a..000000000 Binary files a/webroot/img/emoji/sushiparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/tacoparrot.gif b/webroot/img/emoji/tacoparrot.gif deleted file mode 100644 index aed1d15d6..000000000 Binary files a/webroot/img/emoji/tacoparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/tennisparrot.gif b/webroot/img/emoji/tennisparrot.gif deleted file mode 100644 index 97efeddd1..000000000 Binary files a/webroot/img/emoji/tennisparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/thanks.png b/webroot/img/emoji/thanks.png deleted file mode 100644 index 80e0d32cb..000000000 Binary files a/webroot/img/emoji/thanks.png and /dev/null differ diff --git a/webroot/img/emoji/thumbsupparrot.gif b/webroot/img/emoji/thumbsupparrot.gif deleted file mode 100644 index df3792194..000000000 Binary files a/webroot/img/emoji/thumbsupparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/tiedyeparrot.gif b/webroot/img/emoji/tiedyeparrot.gif deleted file mode 100644 index b9a138efa..000000000 Binary files a/webroot/img/emoji/tiedyeparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/tpparrot.gif b/webroot/img/emoji/tpparrot.gif deleted file mode 100644 index a64738427..000000000 Binary files a/webroot/img/emoji/tpparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/transparront.gif b/webroot/img/emoji/transparront.gif deleted file mode 100644 index f9ecf8b37..000000000 Binary files a/webroot/img/emoji/transparront.gif and /dev/null differ diff --git a/webroot/img/emoji/twinsparrot.gif b/webroot/img/emoji/twinsparrot.gif deleted file mode 100644 index c503979df..000000000 Binary files a/webroot/img/emoji/twinsparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/upvoteparrot.gif b/webroot/img/emoji/upvoteparrot.gif deleted file mode 100644 index f4c8fb417..000000000 Binary files a/webroot/img/emoji/upvoteparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/vikingparrot.gif b/webroot/img/emoji/vikingparrot.gif deleted file mode 100644 index 049e112ca..000000000 Binary files a/webroot/img/emoji/vikingparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/wesmart.png b/webroot/img/emoji/wesmart.png deleted file mode 100644 index 5676faf23..000000000 Binary files a/webroot/img/emoji/wesmart.png and /dev/null differ diff --git a/webroot/img/emoji/wfhparrot.gif b/webroot/img/emoji/wfhparrot.gif deleted file mode 100644 index 02469db93..000000000 Binary files a/webroot/img/emoji/wfhparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/wineparrot.gif b/webroot/img/emoji/wineparrot.gif deleted file mode 100644 index e726f3c7d..000000000 Binary files a/webroot/img/emoji/wineparrot.gif and /dev/null differ diff --git a/webroot/img/emoji/yoda.gif b/webroot/img/emoji/yoda.gif deleted file mode 100644 index 117958c7f..000000000 Binary files a/webroot/img/emoji/yoda.gif and /dev/null differ diff --git a/webroot/img/fediverse-black.png b/webroot/img/fediverse-black.png deleted file mode 100644 index 1bc6a79b6..000000000 Binary files a/webroot/img/fediverse-black.png and /dev/null differ diff --git a/webroot/img/fediverse-color.png b/webroot/img/fediverse-color.png deleted file mode 100644 index bb19584a8..000000000 Binary files a/webroot/img/fediverse-color.png and /dev/null differ diff --git a/webroot/img/fediverse-white.png b/webroot/img/fediverse-white.png deleted file mode 100644 index 63e770f4a..000000000 Binary files a/webroot/img/fediverse-white.png and /dev/null differ diff --git a/webroot/img/follow.svg b/webroot/img/follow.svg deleted file mode 100644 index 35a71d77b..000000000 --- a/webroot/img/follow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/hide-message-grey.svg b/webroot/img/hide-message-grey.svg deleted file mode 100644 index a09ee15cf..000000000 --- a/webroot/img/hide-message-grey.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/hide-message.svg b/webroot/img/hide-message.svg deleted file mode 100644 index 1d2c562a3..000000000 --- a/webroot/img/hide-message.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/indieauth.png b/webroot/img/indieauth.png deleted file mode 100644 index 8f1beee9c..000000000 Binary files a/webroot/img/indieauth.png and /dev/null differ diff --git a/webroot/img/like.svg b/webroot/img/like.svg deleted file mode 100644 index e03908be0..000000000 --- a/webroot/img/like.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/loading.gif b/webroot/img/loading.gif deleted file mode 100644 index b6ae174e8..000000000 Binary files a/webroot/img/loading.gif and /dev/null differ diff --git a/webroot/img/logo.png b/webroot/img/logo.png deleted file mode 100644 index fa10960e6..000000000 Binary files a/webroot/img/logo.png and /dev/null differ diff --git a/webroot/img/logo.svg b/webroot/img/logo.svg deleted file mode 100644 index ac3f9b784..000000000 --- a/webroot/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/menu-filled.svg b/webroot/img/menu-filled.svg deleted file mode 100644 index db16ab78f..000000000 --- a/webroot/img/menu-filled.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/menu-vert.svg b/webroot/img/menu-vert.svg deleted file mode 100644 index 1a1876e43..000000000 --- a/webroot/img/menu-vert.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/menu.svg b/webroot/img/menu.svg deleted file mode 100644 index 1e52e02c2..000000000 --- a/webroot/img/menu.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/moderator-grey.svg b/webroot/img/moderator-grey.svg deleted file mode 100644 index 1fa27fc89..000000000 --- a/webroot/img/moderator-grey.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/moderator-nobackground.svg b/webroot/img/moderator-nobackground.svg deleted file mode 100644 index edc18fe72..000000000 --- a/webroot/img/moderator-nobackground.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/moderator.svg b/webroot/img/moderator.svg deleted file mode 100644 index 24940eb97..000000000 --- a/webroot/img/moderator.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/notification-bell.svg b/webroot/img/notification-bell.svg deleted file mode 100644 index be84e5224..000000000 --- a/webroot/img/notification-bell.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/owncast-background.png b/webroot/img/owncast-background.png deleted file mode 100644 index ddacbc49d..000000000 Binary files a/webroot/img/owncast-background.png and /dev/null differ diff --git a/webroot/img/repost.svg b/webroot/img/repost.svg deleted file mode 100644 index 2d5dd0a20..000000000 --- a/webroot/img/repost.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/smiley.png b/webroot/img/smiley.png deleted file mode 100644 index 5d814909a..000000000 Binary files a/webroot/img/smiley.png and /dev/null differ diff --git a/webroot/img/user-icon.svg b/webroot/img/user-icon.svg deleted file mode 100644 index bbb5e9d15..000000000 --- a/webroot/img/user-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/webroot/img/user-settings.svg b/webroot/img/user-settings.svg deleted file mode 100644 index 868c7c003..000000000 --- a/webroot/img/user-settings.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/webroot/index-standalone-chat-readonly.html b/webroot/index-standalone-chat-readonly.html deleted file mode 100644 index e1e9d4258..000000000 --- a/webroot/index-standalone-chat-readonly.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - -
- - - - diff --git a/webroot/index-standalone-chat-readwrite.html b/webroot/index-standalone-chat-readwrite.html deleted file mode 100644 index 159454cac..000000000 --- a/webroot/index-standalone-chat-readwrite.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - -
- - - - diff --git a/webroot/index-standalone-chat.html b/webroot/index-standalone-chat.html deleted file mode 120000 index bba6dc07c..000000000 --- a/webroot/index-standalone-chat.html +++ /dev/null @@ -1 +0,0 @@ -index-standalone-chat-readonly.html \ No newline at end of file diff --git a/webroot/index-video-only.html b/webroot/index-video-only.html deleted file mode 100644 index c02acd6e1..000000000 --- a/webroot/index-video-only.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - -
- - - - diff --git a/webroot/index.html b/webroot/index.html deleted file mode 100644 index 499dcc87e..000000000 --- a/webroot/index.html +++ /dev/null @@ -1,261 +0,0 @@ - - - - Owncast - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- - - - - - diff --git a/webroot/js/app-standalone-chat.js b/webroot/js/app-standalone-chat.js deleted file mode 100644 index 227a86ad6..000000000 --- a/webroot/js/app-standalone-chat.js +++ /dev/null @@ -1,343 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); -import UsernameForm from './components/chat/username.js'; -import Chat from './components/chat/chat.js'; -import Websocket, { - CALLBACKS, - SOCKET_MESSAGE_TYPES, -} from './utils/websocket.js'; -import { registerChat } from './chat/register.js'; -import { getLocalStorage, setLocalStorage } from './utils/helpers.js'; -import { - CHAT_MAX_MESSAGE_LENGTH, - EST_SOCKET_PAYLOAD_BUFFER, - KEY_EMBED_CHAT_ACCESS_TOKEN, - KEY_ACCESS_TOKEN, - KEY_USERNAME, - TIMER_DISABLE_CHAT_AFTER_OFFLINE, - URL_STATUS, - URL_CONFIG, - TIMER_STATUS_UPDATE, -} from './utils/constants.js'; -import { URL_WEBSOCKET } from './utils/constants.js'; - -export default class StandaloneChat extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - websocket: null, - canChat: false, - chatEnabled: true, // always true for standalone chat - chatInputEnabled: false, // chat input box state - accessToken: null, - username: null, - isRegistering: false, - streamOnline: null, // stream is active/online - lastDisconnectTime: null, - configData: { - loading: true, - }, - }; - this.disableChatInputTimer = null; - this.hasConfiguredChat = false; - - this.handleUsernameChange = this.handleUsernameChange.bind(this); - this.handleOfflineMode = this.handleOfflineMode.bind(this); - this.handleOnlineMode = this.handleOnlineMode.bind(this); - this.handleFormFocus = this.handleFormFocus.bind(this); - this.handleFormBlur = this.handleFormBlur.bind(this); - this.getStreamStatus = this.getStreamStatus.bind(this); - this.getConfig = this.getConfig.bind(this); - this.disableChatInput = this.disableChatInput.bind(this); - this.setupChatAuth = this.setupChatAuth.bind(this); - this.disableChat = this.disableChat.bind(this); - - this.socketHostOverride = null; - - // user events - this.handleWebsocketMessage = this.handleWebsocketMessage.bind(this); - - this.getConfig(); - - this.getStreamStatus(); - this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE); - } - - // fetch /config data - getConfig() { - fetch(URL_CONFIG) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.setConfigData(json); - }) - .catch((error) => { - this.handleNetworkingError(`Fetch config: ${error}`); - }); - } - - // fetch stream status - getStreamStatus() { - fetch(URL_STATUS) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.updateStreamStatus(json); - }) - .catch((error) => { - this.handleOfflineMode(); - this.handleNetworkingError(`Stream status: ${error}`); - }); - } - - setConfigData(data = {}) { - const { chatDisabled, socketHostOverride } = data; - - // If this is the first time setting the config - // then setup chat if it's enabled. - if (!this.hasConfiguredChat && !chatDisabled) { - this.setupChatAuth(); - } - - this.hasConfiguredChat = true; - this.socketHostOverride = socketHostOverride; - this.setState({ - canChat: !chatDisabled, - configData: { - ...data, - }, - }); - } - - // handle UI things from stream status result - updateStreamStatus(status = {}) { - const { streamOnline: curStreamOnline } = this.state; - - if (!status) { - return; - } - const { online, lastDisconnectTime } = status; - - this.setState({ - lastDisconnectTime, - streamOnline: online, - }); - - if (status.online !== curStreamOnline) { - if (status.online) { - // stream has just come online. - this.handleOnlineMode(); - } else { - // stream has just flipped offline or app just got loaded and stream is offline. - this.handleOfflineMode(lastDisconnectTime); - } - } - } - - // stop status timer and disable chat after some time. - handleOfflineMode(lastDisconnectTime) { - if (lastDisconnectTime) { - const remainingChatTime = - TIMER_DISABLE_CHAT_AFTER_OFFLINE - - (Date.now() - new Date(lastDisconnectTime)); - const countdown = remainingChatTime < 0 ? 0 : remainingChatTime; - if (countdown > 0) { - this.setState({ - chatInputEnabled: true, - }); - } - this.disableChatInputTimer = setTimeout(this.disableChatInput, countdown); - } - this.setState({ - streamOnline: false, - }); - } - - handleOnlineMode() { - clearTimeout(this.disableChatInputTimer); - this.disableChatInputTimer = null; - - this.setState({ - streamOnline: true, - chatInputEnabled: true, - }); - } - - handleUsernameChange(newName) { - this.setState({ - username: newName, - }); - this.sendUsernameChange(newName); - } - - disableChatInput() { - this.setState({ - chatInputEnabled: false, - }); - } - - handleNetworkingError(error) { - console.error(`>>> App Error: ${error}`); - } - - handleWebsocketMessage(e) { - if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) { - // User has been actively disabled on the backend. Turn off chat for them. - this.handleBlockedChat(); - } else if ( - e.type === SOCKET_MESSAGE_TYPES.ERROR_NEEDS_REGISTRATION && - !this.isRegistering - ) { - // User needs an access token, so start the user auth flow. - this.state.websocket.shutdown(); - this.setState({ websocket: null }); - this.setupChatAuth(true); - } else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_MAX_CONNECTIONS_EXCEEDED) { - // Chat server cannot support any more chat clients. Turn off chat for them. - this.disableChat(); - } else if (e.type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { - // When connected the user will return an event letting us know what our - // user details are so we can display them properly. - const { user } = e; - const { displayName } = user; - - this.setState({ username: displayName }); - } - } - - handleBlockedChat() { - setLocalStorage('owncast_chat_blocked', true); - this.disableChat(); - } - - handleFormFocus() { - if (this.hasTouchScreen) { - this.setState({ - touchKeyboardActive: true, - }); - } - } - - handleFormBlur() { - if (this.hasTouchScreen) { - this.setState({ - touchKeyboardActive: false, - }); - } - } - - disableChat() { - this.state.websocket.shutdown(); - this.setState({ websocket: null, canChat: false }); - } - - async setupChatAuth(force) { - const { readonly } = this.props; - var accessToken = readonly - ? getLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN) - : getLocalStorage(KEY_ACCESS_TOKEN); - var randomIntArray = new Uint32Array(1); - window.crypto.getRandomValues(randomIntArray); - var username = readonly - ? 'chat-embed-' + randomIntArray[0] - : getLocalStorage(KEY_USERNAME); - - if (!accessToken || force) { - try { - this.isRegistering = true; - const registration = await registerChat(username); - accessToken = registration.accessToken; - username = registration.displayName; - - if (readonly) { - setLocalStorage(KEY_EMBED_CHAT_ACCESS_TOKEN, accessToken); - } else { - setLocalStorage(KEY_ACCESS_TOKEN, accessToken); - setLocalStorage(KEY_USERNAME, username); - } - - this.isRegistering = false; - } catch (e) { - console.error('registration error:', e); - } - } - - if (this.state.websocket) { - this.state.websocket.shutdown(); - this.setState({ - websocket: null, - }); - } - - // Without a valid access token he websocket connection will be rejected. - const websocket = new Websocket( - accessToken, - this.socketHostOverride || URL_WEBSOCKET - ); - websocket.addListener( - CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, - this.handleWebsocketMessage - ); - - this.setState({ - username, - websocket, - accessToken, - }); - } - - sendUsernameChange(newName) { - const nameChange = { - type: SOCKET_MESSAGE_TYPES.NAME_CHANGE, - newName, - }; - this.state.websocket.send(nameChange); - } - - render(props, state) { - const { username, websocket, accessToken, chatInputEnabled, configData } = - state; - - const { chatDisabled, maxSocketPayloadSize, customStyles, name } = - configData; - - const { readonly } = props; - return this.state.websocket - ? html`${!readonly - ? html` -
- <${UsernameForm} - username=${username} - onUsernameChange=${this.handleUsernameChange} - onFocus=${this.handleFormFocus} - onBlur=${this.handleFormBlur} - /> -
` - : ''} - <${Chat} - websocket=${websocket} - username=${username} - accessToken=${accessToken} - readonly=${readonly} - instanceTitle=${name} - chatInputEnabled=${chatInputEnabled && !chatDisabled} - inputMaxBytes=${maxSocketPayloadSize - EST_SOCKET_PAYLOAD_BUFFER || - CHAT_MAX_MESSAGE_LENGTH} - />` - : null; - } -} diff --git a/webroot/js/app-video-only.js b/webroot/js/app-video-only.js deleted file mode 100644 index cc618ef8f..000000000 --- a/webroot/js/app-video-only.js +++ /dev/null @@ -1,287 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import VideoPoster from './components/video-poster.js'; -import { OwncastPlayer } from './components/player.js'; - -import { - addNewlines, - makeLastOnlineString, - pluralize, - parseSecondsToDurationString, -} from './utils/helpers.js'; -import { - URL_CONFIG, - URL_STATUS, - URL_VIEWER_PING, - TIMER_STATUS_UPDATE, - TIMER_STREAM_DURATION_COUNTER, - TEMP_IMAGE, - MESSAGE_OFFLINE, - MESSAGE_ONLINE, -} from './utils/constants.js'; - -export default class VideoOnly extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - configData: {}, - - playerActive: false, // player object is active - streamOnline: false, // stream is active/online - - isPlaying: false, - - //status - streamStatusMessage: MESSAGE_OFFLINE, - viewerCount: '', - lastDisconnectTime: null, - }; - - // timers - this.playerRestartTimer = null; - this.offlineTimer = null; - this.statusTimer = null; - this.streamDurationTimer = null; - - this.handleOfflineMode = this.handleOfflineMode.bind(this); - this.handleOnlineMode = this.handleOnlineMode.bind(this); - this.setCurrentStreamDuration = this.setCurrentStreamDuration.bind(this); - - // player events - this.handlePlayerReady = this.handlePlayerReady.bind(this); - this.handlePlayerPlaying = this.handlePlayerPlaying.bind(this); - this.handlePlayerEnded = this.handlePlayerEnded.bind(this); - this.handlePlayerError = this.handlePlayerError.bind(this); - - // fetch events - this.getConfig = this.getConfig.bind(this); - this.getStreamStatus = this.getStreamStatus.bind(this); - } - - componentDidMount() { - this.getConfig(); - - this.player = new OwncastPlayer(); - this.player.setupPlayerCallbacks({ - onReady: this.handlePlayerReady, - onPlaying: this.handlePlayerPlaying, - onEnded: this.handlePlayerEnded, - onError: this.handlePlayerError, - }); - this.player.init(); - } - - componentWillUnmount() { - // clear all the timers - clearInterval(this.playerRestartTimer); - clearInterval(this.offlineTimer); - clearInterval(this.statusTimer); - clearInterval(this.streamDurationTimer); - } - - // fetch /config data - getConfig() { - fetch(URL_CONFIG) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.setConfigData(json); - }) - .catch((error) => { - this.handleNetworkingError(`Fetch config: ${error}`); - }); - } - - // fetch stream status - getStreamStatus() { - fetch(URL_STATUS) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.updateStreamStatus(json); - }) - .catch((error) => { - this.handleOfflineMode(); - this.handleNetworkingError(`Stream status: ${error}`); - }); - - // Ping the API to let them know we're an active viewer - fetch(URL_VIEWER_PING).catch((error) => { - this.handleOfflineMode(); - this.handleNetworkingError(`Viewer PING error: ${error}`); - }); - } - - setConfigData(data = {}) { - const { title, summary } = data; - window.document.title = title; - this.setState({ - configData: { - ...data, - summary: summary && addNewlines(summary), - }, - }); - } - - // handle UI things from stream status result - updateStreamStatus(status = {}) { - const { streamOnline: curStreamOnline } = this.state; - - if (!status) { - return; - } - const { viewerCount, online, lastConnectTime, lastDisconnectTime } = status; - - if (status.online && !curStreamOnline) { - // stream has just come online. - this.handleOnlineMode(); - } else if (!status.online && curStreamOnline) { - // stream has just flipped offline. - this.handleOfflineMode(); - } - this.setState({ - viewerCount, - streamOnline: online, - lastDisconnectTime, - lastConnectTime, - }); - } - - // when videojs player is ready, start polling for stream - handlePlayerReady() { - this.getStreamStatus(); - this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE); - } - - handlePlayerPlaying() { - this.setState({ - isPlaying: true, - }); - } - - // likely called some time after stream status has gone offline. - // basically hide video and show underlying "poster" - handlePlayerEnded() { - this.setState({ - playerActive: false, - isPlaying: false, - }); - } - - handlePlayerError() { - // do something? - this.handleOfflineMode(); - this.handlePlayerEnded(); - } - - // stop status timer and disable chat after some time. - handleOfflineMode() { - clearInterval(this.streamDurationTimer); - this.setState({ - streamOnline: false, - streamStatusMessage: MESSAGE_OFFLINE, - }); - } - - setCurrentStreamDuration() { - let streamDurationString = ''; - if (this.state.lastConnectTime) { - const diff = (Date.now() - Date.parse(this.state.lastConnectTime)) / 1000; - streamDurationString = parseSecondsToDurationString(diff); - } - this.setState({ - streamStatusMessage: `${MESSAGE_ONLINE} ${streamDurationString}`, - }); - } - - // play video! - handleOnlineMode() { - this.player.startPlayer(); - - this.streamDurationTimer = setInterval( - this.setCurrentStreamDuration, - TIMER_STREAM_DURATION_COUNTER - ); - - this.setState({ - playerActive: true, - streamOnline: true, - streamStatusMessage: MESSAGE_ONLINE, - }); - } - - handleNetworkingError(error) { - console.error(`>>> App Error: ${error}`); - } - - render(props, state) { - const { - configData, - - viewerCount, - playerActive, - streamOnline, - streamStatusMessage, - lastDisconnectTime, - isPlaying, - } = state; - - const { logo = TEMP_IMAGE, customStyles } = configData; - - let viewerCountMessage = ''; - if (streamOnline && viewerCount > 0) { - viewerCountMessage = html`${viewerCount} - ${pluralize(' viewer', viewerCount)}`; - } else if (lastDisconnectTime) { - viewerCountMessage = makeLastOnlineString(lastDisconnectTime); - } - - const mainClass = playerActive ? 'online' : ''; - - const poster = isPlaying - ? null - : html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `; - return html` -
- -
- - ${poster} -
- -
- ${streamStatusMessage} - ${viewerCountMessage} -
-
- `; - } -} diff --git a/webroot/js/app.js b/webroot/js/app.js deleted file mode 100644 index 671847b40..000000000 --- a/webroot/js/app.js +++ /dev/null @@ -1,1094 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import { URL_WEBSOCKET } from './utils/constants.js'; - -import { OwncastPlayer } from './components/player.js'; -import SocialIconsList from './components/platform-logos-list.js'; -import VideoPoster from './components/video-poster.js'; -import Followers from './components/federation/followers.js'; -import Chat from './components/chat/chat.js'; -import { ChatMenu } from './components/chat/chat-menu.js'; -import Websocket, { - CALLBACKS, - SOCKET_MESSAGE_TYPES, -} from './utils/websocket.js'; -import { registerChat } from './chat/register.js'; - -import ExternalActionModal, { - ExternalActionButton, -} from './components/external-action-modal.js'; - -import FediverseFollowModal, { - FediverseFollowButton, -} from './components/fediverse-follow-modal.js'; - -import { NotifyButton, NotifyModal } from './components/notification.js'; -import { isPushNotificationSupported } from './notification/registerWeb.js'; -import ChatSettingsModal from './components/chat-settings-modal.js'; - -import { - addNewlines, - checkUrlPathForDisplay, - classNames, - debounce, - getLocalStorage, - getOrientation, - hasTouchScreen, - makeLastOnlineString, - parseSecondsToDurationString, - pluralize, - ROUTE_RECORDINGS, - setLocalStorage, -} from './utils/helpers.js'; -import { - CHAT_MAX_MESSAGE_LENGTH, - EST_SOCKET_PAYLOAD_BUFFER, - HEIGHT_SHORT_WIDE, - KEY_ACCESS_TOKEN, - KEY_CHAT_DISPLAYED, - KEY_USERNAME, - MESSAGE_OFFLINE, - MESSAGE_ONLINE, - ORIENTATION_PORTRAIT, - OWNCAST_LOGO_LOCAL, - TEMP_IMAGE, - TIMER_DISABLE_CHAT_AFTER_OFFLINE, - TIMER_STATUS_UPDATE, - TIMER_STREAM_DURATION_COUNTER, - URL_CONFIG, - URL_OWNCAST, - URL_STATUS, - URL_VIEWER_PING, - WIDTH_SINGLE_COL, - USER_VISIT_COUNT_KEY, -} from './utils/constants.js'; -import { checkIsModerator } from './utils/chat.js'; - -import TabBar from './components/tab-bar.js'; - -export default class App extends Component { - constructor(props, context) { - super(props, context); - - const chatStorage = getLocalStorage(KEY_CHAT_DISPLAYED); - this.hasTouchScreen = hasTouchScreen(); - this.windowBlurred = false; - - this.state = { - websocket: null, - canChat: false, // all of chat functionality (panel + username) - displayChatPanel: chatStorage === null ? true : chatStorage === 'true', // just the chat panel - chatInputEnabled: false, // chat input box state - accessToken: null, - username: getLocalStorage(KEY_USERNAME), - isModerator: false, - - isRegistering: false, - touchKeyboardActive: false, - - configData: { - loading: true, - }, - extraPageContent: '', - - playerActive: false, // player object is active - streamOnline: null, // stream is active/online - isPlaying: false, // player is actively playing video - - // status - streamStatusMessage: MESSAGE_OFFLINE, - viewerCount: '', - lastDisconnectTime: null, - - // dom - windowWidth: window.innerWidth, - windowHeight: window.innerHeight, - orientation: getOrientation(this.hasTouchScreen), - - // modals - externalActionModalData: null, - fediverseModalData: null, - - // authentication options - indieAuthEnabled: false, - - // routing & tabbing - section: '', - sectionId: '', - }; - - // timers - this.playerRestartTimer = null; - this.offlineTimer = null; - this.statusTimer = null; - this.disableChatInputTimer = null; - this.streamDurationTimer = null; - - // misc dom events - this.handleChatPanelToggle = this.handleChatPanelToggle.bind(this); - this.handleUsernameChange = this.handleUsernameChange.bind(this); - this.handleFormFocus = this.handleFormFocus.bind(this); - this.handleFormBlur = this.handleFormBlur.bind(this); - this.handleWindowBlur = this.handleWindowBlur.bind(this); - this.handleWindowFocus = this.handleWindowFocus.bind(this); - this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 250); - - this.handleOfflineMode = this.handleOfflineMode.bind(this); - this.handleOnlineMode = this.handleOnlineMode.bind(this); - this.disableChatInput = this.disableChatInput.bind(this); - this.setCurrentStreamDuration = this.setCurrentStreamDuration.bind(this); - - this.handleKeyPressed = this.handleKeyPressed.bind(this); - this.displayExternalAction = this.displayExternalAction.bind(this); - this.closeExternalActionModal = this.closeExternalActionModal.bind(this); - this.displayFediverseFollowModal = - this.displayFediverseFollowModal.bind(this); - this.closeFediverseFollowModal = this.closeFediverseFollowModal.bind(this); - this.displayNotificationModal = this.displayNotificationModal.bind(this); - this.closeNotificationModal = this.closeNotificationModal.bind(this); - this.showAuthModal = this.showAuthModal.bind(this); - this.closeAuthModal = this.closeAuthModal.bind(this); - - // player events - this.handlePlayerReady = this.handlePlayerReady.bind(this); - this.handlePlayerPlaying = this.handlePlayerPlaying.bind(this); - this.handlePlayerEnded = this.handlePlayerEnded.bind(this); - this.handlePlayerError = this.handlePlayerError.bind(this); - - // fetch events - this.getConfig = this.getConfig.bind(this); - this.getStreamStatus = this.getStreamStatus.bind(this); - - // user events - this.handleWebsocketMessage = this.handleWebsocketMessage.bind(this); - - // chat - this.hasConfiguredChat = false; - this.setupChatAuth = this.setupChatAuth.bind(this); - this.disableChat = this.disableChat.bind(this); - this.socketHostOverride = null; - } - - componentDidMount() { - this.getConfig(); - if (!this.hasTouchScreen) { - window.addEventListener('resize', this.handleWindowResize); - } - window.addEventListener('blur', this.handleWindowBlur); - window.addEventListener('focus', this.handleWindowFocus); - if (this.hasTouchScreen) { - window.addEventListener('orientationchange', this.handleWindowResize); - } - window.addEventListener('keypress', this.handleKeyPressed); - this.player = new OwncastPlayer(); - this.player.setupPlayerCallbacks({ - onReady: this.handlePlayerReady, - onPlaying: this.handlePlayerPlaying, - onEnded: this.handlePlayerEnded, - onError: this.handlePlayerError, - }); - this.player.init(); - - this.registerServiceWorker(); - - // check routing - this.getRoute(); - - // Increment the visit counter - this.incrementVisitCounter(); - } - - incrementVisitCounter() { - let visits = parseInt(getLocalStorage(USER_VISIT_COUNT_KEY)); - if (isNaN(visits)) { - visits = 0; - } - - setLocalStorage(USER_VISIT_COUNT_KEY, visits + 1); - } - - componentWillUnmount() { - // clear all the timers - clearInterval(this.playerRestartTimer); - clearInterval(this.offlineTimer); - clearInterval(this.statusTimer); - clearTimeout(this.disableChatInputTimer); - clearInterval(this.streamDurationTimer); - window.removeEventListener('resize', this.handleWindowResize); - window.removeEventListener('blur', this.handleWindowBlur); - window.removeEventListener('focus', this.handleWindowFocus); - window.removeEventListener('keypress', this.handleKeyPressed); - if (this.hasTouchScreen) { - window.removeEventListener('orientationchange', this.handleWindowResize); - } - } - - getRoute() { - const routeInfo = checkUrlPathForDisplay(); - this.setState({ - ...routeInfo, - }); - } - - // fetch /config data - getConfig() { - fetch(URL_CONFIG) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.setConfigData(json); - }) - .catch((error) => { - this.handleNetworkingError(`Fetch config: ${error}`); - }); - } - - // fetch stream status - getStreamStatus() { - fetch(URL_STATUS) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - this.updateStreamStatus(json); - }) - .catch((error) => { - this.handleOfflineMode(); - this.handleNetworkingError(`Stream status: ${error}`); - }); - - // Ping the API to let them know we're an active viewer - fetch(URL_VIEWER_PING).catch((error) => { - this.handleOfflineMode(); - this.handleNetworkingError(`Viewer PING error: ${error}`); - }); - } - - setConfigData(data = {}) { - const { - name, - summary, - chatDisabled, - socketHostOverride, - notifications, - authentication, - } = data; - window.document.title = name; - - this.socketHostOverride = socketHostOverride; - - // If this is the first time setting the config - // then setup chat if it's enabled. - if (!this.hasConfiguredChat && !chatDisabled) { - this.setupChatAuth(); - } - - this.hasConfiguredChat = true; - const { indieAuthEnabled } = authentication; - - this.setState({ - canChat: !chatDisabled, - notifications, - indieAuthEnabled, - configData: { - ...data, - summary: summary && addNewlines(summary), - }, - }); - } - - // handle UI things from stream status result - updateStreamStatus(status = {}) { - const { streamOnline: curStreamOnline } = this.state; - - if (!status) { - return; - } - const { - viewerCount, - online, - lastConnectTime, - streamTitle, - lastDisconnectTime, - serverTime, - } = status; - - const clockSkew = new Date(serverTime).getTime() - Date.now(); - this.player.setClockSkew(clockSkew); - - this.setState({ - viewerCount, - lastConnectTime, - streamOnline: online, - streamTitle, - lastDisconnectTime, - }); - - if (status.online !== curStreamOnline) { - if (status.online) { - // stream has just come online. - this.handleOnlineMode(); - } else { - // stream has just flipped offline or app just got loaded and stream is offline. - this.handleOfflineMode(lastDisconnectTime); - } - } - } - - // when videojs player is ready, start polling for stream - handlePlayerReady() { - this.getStreamStatus(); - this.statusTimer = setInterval(this.getStreamStatus, TIMER_STATUS_UPDATE); - } - - handlePlayerPlaying() { - this.setState({ - isPlaying: true, - }); - } - - // likely called some time after stream status has gone offline. - // basically hide video and show underlying "poster" - handlePlayerEnded() { - this.setState({ - playerActive: false, - isPlaying: false, - }); - } - - handlePlayerError() { - // do something? - this.handleOfflineMode(); - this.handlePlayerEnded(); - } - - // stop status timer and disable chat after some time. - handleOfflineMode(lastDisconnectTime) { - clearInterval(this.streamDurationTimer); - - if (lastDisconnectTime) { - const remainingChatTime = - TIMER_DISABLE_CHAT_AFTER_OFFLINE - - (Date.now() - new Date(lastDisconnectTime)); - const countdown = remainingChatTime < 0 ? 0 : remainingChatTime; - if (countdown > 0) { - this.setState({ - chatInputEnabled: true, - }); - } - this.disableChatInputTimer = setTimeout(this.disableChatInput, countdown); - } - - this.setState({ - streamOnline: false, - streamStatusMessage: MESSAGE_OFFLINE, - }); - - if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) { - this.handlePlayerEnded(); - } - - if (this.windowBlurred) { - document.title = ` 🔴 ${ - this.state.configData && this.state.configData.name - }`; - } - } - - // play video! - handleOnlineMode() { - this.player.startPlayer(); - clearTimeout(this.disableChatInputTimer); - this.disableChatInputTimer = null; - - this.streamDurationTimer = setInterval( - this.setCurrentStreamDuration, - TIMER_STREAM_DURATION_COUNTER - ); - - this.setState({ - playerActive: true, - streamOnline: true, - chatInputEnabled: true, - streamTitle: '', - streamStatusMessage: MESSAGE_ONLINE, - }); - - if (this.windowBlurred) { - document.title = ` 🟢 ${ - this.state.configData && this.state.configData.name - }`; - } - } - - setCurrentStreamDuration() { - let streamDurationString = ''; - if (this.state.lastConnectTime) { - const diff = (Date.now() - Date.parse(this.state.lastConnectTime)) / 1000; - streamDurationString = parseSecondsToDurationString(diff); - } - this.setState({ - streamStatusMessage: `${MESSAGE_ONLINE} ${streamDurationString}`, - }); - } - - handleUsernameChange(newName) { - this.setState({ - username: newName, - }); - - this.sendUsernameChange(newName); - } - - handleFormFocus() { - if (this.hasTouchScreen) { - this.setState({ - touchKeyboardActive: true, - }); - } - } - - handleFormBlur() { - if (this.hasTouchScreen) { - this.setState({ - touchKeyboardActive: false, - }); - } - } - - handleChatPanelToggle() { - const { displayChatPanel: curDisplayed } = this.state; - - const displayChat = !curDisplayed; - setLocalStorage(KEY_CHAT_DISPLAYED, displayChat); - this.setState({ - displayChatPanel: displayChat, - }); - } - - disableChatInput() { - this.setState({ - chatInputEnabled: false, - }); - } - - handleNetworkingError(error) { - console.error(`>>> App Error: ${error}`); - } - - handleWindowResize() { - this.setState({ - windowWidth: window.innerWidth, - windowHeight: window.innerHeight, - orientation: getOrientation(this.hasTouchScreen), - }); - } - - handleWindowBlur() { - this.windowBlurred = true; - } - - handleWindowFocus() { - this.windowBlurred = false; - window.document.title = this.state.configData && this.state.configData.name; - } - - handleSpaceBarPressed(e) { - e.preventDefault(); - if (this.state.isPlaying) { - this.setState({ - isPlaying: false, - }); - try { - this.player.vjsPlayer.pause(); - } catch (err) { - console.warn(err); - } - } else { - this.setState({ - isPlaying: true, - }); - this.player.vjsPlayer.play(); - } - } - - handleMuteKeyPressed() { - const muted = this.player.vjsPlayer.muted(); - const volume = this.player.vjsPlayer.volume(); - - try { - if (volume === 0) { - this.player.vjsPlayer.volume(0.5); - this.player.vjsPlayer.muted(false); - } else { - this.player.vjsPlayer.muted(!muted); - } - } catch (err) { - console.warn(err); - } - } - - handleFullScreenKeyPressed() { - if (this.player.vjsPlayer.isFullscreen()) { - this.player.vjsPlayer.exitFullscreen(); - } else { - this.player.vjsPlayer.requestFullscreen(); - } - } - - handleVolumeSet(factor) { - this.player.vjsPlayer.volume(this.player.vjsPlayer.volume() + factor); - } - - handleKeyPressed(e) { - // Only handle shortcuts if the focus is on the general page body, - // not a specific input field. - if (e.target !== document.getElementById('app-body')) { - return; - } - - if (this.state.streamOnline) { - switch (e.code) { - case 'MediaPlayPause': - case 'KeyP': - case 'Space': - this.handleSpaceBarPressed(e); - break; - case 'KeyM': - this.handleMuteKeyPressed(e); - break; - case 'KeyF': - this.handleFullScreenKeyPressed(e); - break; - case 'KeyC': - this.handleChatPanelToggle(); - break; - case 'Digit9': - this.handleVolumeSet(-0.1); - break; - case 'Digit0': - this.handleVolumeSet(0.1); - } - } - } - - displayExternalAction(action) { - const { username } = this.state; - if (!action) { - return; - } - const { url: actionUrl, openExternally } = action || {}; - let url = new URL(actionUrl); - // Append url and username to params so the link knows where we came from and who we are. - url.searchParams.append('username', username); - url.searchParams.append('instance', window.location); - - const fullUrl = url.toString(); - - if (openExternally) { - var win = window.open(fullUrl, '_blank'); - win.focus(); - return; - } - this.setState({ - externalActionModalData: { - ...action, - url: fullUrl, - }, - }); - } - closeExternalActionModal() { - this.setState({ - externalActionModalData: null, - }); - } - - displayFediverseFollowModal(data) { - this.setState({ fediverseModalData: data }); - } - closeFediverseFollowModal() { - this.setState({ fediverseModalData: null }); - } - - displayNotificationModal(data) { - this.setState({ notificationModalData: data }); - } - closeNotificationModal() { - this.setState({ notificationModalData: null }); - } - - async registerServiceWorker() { - try { - const reg = await navigator.serviceWorker.register('/serviceWorker.js', { - scope: '/', - }); - } catch (err) { - console.error('Owncast service worker registration failed!', err); - } - } - - showAuthModal() { - const data = { - title: 'Authenticate with chat', - }; - this.setState({ authModalData: data }); - } - - closeAuthModal() { - this.setState({ authModalData: null }); - } - - handleWebsocketMessage(e) { - if (e.type === SOCKET_MESSAGE_TYPES.ERROR_USER_DISABLED) { - // User has been actively disabled on the backend. Turn off chat for them. - this.handleBlockedChat(); - } else if ( - e.type === SOCKET_MESSAGE_TYPES.ERROR_NEEDS_REGISTRATION && - !this.isRegistering - ) { - // User needs an access token, so start the user auth flow. - this.state.websocket.shutdown(); - this.setState({ websocket: null }); - this.setupChatAuth(true); - } else if (e.type === SOCKET_MESSAGE_TYPES.ERROR_MAX_CONNECTIONS_EXCEEDED) { - // Chat server cannot support any more chat clients. Turn off chat for them. - this.disableChat(); - } else if (e.type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { - // When connected the user will return an event letting us know what our - // user details are so we can display them properly. - const { user } = e; - const { displayName, authenticated } = user; - - this.setState({ - username: displayName, - authenticated, - isModerator: checkIsModerator(e), - }); - } - } - - handleBlockedChat() { - this.disableChat(); - } - - disableChat() { - this.state.websocket.shutdown(); - this.setState({ websocket: null, canChat: false }); - } - - async setupChatAuth(force) { - var accessToken = getLocalStorage(KEY_ACCESS_TOKEN); - var username = getLocalStorage(KEY_USERNAME); - - if (!accessToken || force) { - try { - this.isRegistering = true; - const registration = await registerChat(this.state.username); - accessToken = registration.accessToken; - username = registration.displayName; - - setLocalStorage(KEY_ACCESS_TOKEN, accessToken); - setLocalStorage(KEY_USERNAME, username); - - this.isRegistering = false; - } catch (e) { - console.error('registration error:', e); - } - } - - if (this.state.websocket) { - this.state.websocket.shutdown(); - this.setState({ - websocket: null, - }); - } - - // Without a valid access token the websocket connection will be rejected. - const websocket = new Websocket( - accessToken, - this.socketHostOverride || URL_WEBSOCKET - ); - websocket.addListener( - CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, - this.handleWebsocketMessage - ); - - this.setState({ - username, - websocket, - accessToken, - }); - } - - sendUsernameChange(newName) { - const nameChange = { - type: SOCKET_MESSAGE_TYPES.NAME_CHANGE, - newName, - }; - this.state.websocket.send(nameChange); - } - - render(props, state) { - const { - accessToken, - chatInputEnabled, - configData, - displayChatPanel, - canChat, - isModerator, - - isPlaying, - orientation, - playerActive, - streamOnline, - streamStatusMessage, - streamTitle, - touchKeyboardActive, - username, - authenticated, - viewerCount, - websocket, - windowHeight, - windowWidth, - fediverseModalData, - authModalData, - externalActionModalData, - notificationModalData, - notifications, - lastDisconnectTime, - section, - sectionId, - indieAuthEnabled, - } = state; - - const { - version: appVersion, - logo = TEMP_IMAGE, - socialHandles = [], - summary, - tags = [], - name, - extraPageContent, - chatDisabled, - externalActions, - customStyles, - maxSocketPayloadSize, - federation = {}, - } = configData; - - const bgUserLogo = { backgroundImage: `url(${logo})` }; - - const tagList = tags !== null && tags.length > 0 && tags.join(' #'); - - let viewerCountMessage = ''; - if (streamOnline && viewerCount > 0) { - viewerCountMessage = html`${viewerCount} - ${pluralize(' viewer', viewerCount)}`; - } else if (lastDisconnectTime) { - viewerCountMessage = makeLastOnlineString(lastDisconnectTime); - } - - const mainClass = playerActive ? 'online' : ''; - const isPortrait = - this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT; - const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait; - const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight; - - const noVideoContent = - !playerActive || (section === ROUTE_RECORDINGS && sectionId !== ''); - const shouldDisplayChat = - displayChatPanel && !chatDisabled && chatInputEnabled; - - const extraAppClasses = classNames({ - 'config-loading': configData.loading, - - chat: shouldDisplayChat, - 'no-chat': !shouldDisplayChat, - 'no-video': noVideoContent, - 'chat-hidden': !displayChatPanel && canChat && !chatDisabled, // hide panel - 'chat-disabled': !canChat || chatDisabled, - 'single-col': singleColMode, - 'bg-gray-800': singleColMode && shouldDisplayChat, - 'short-wide': shortHeight && windowWidth > WIDTH_SINGLE_COL, - 'touch-screen': this.hasTouchScreen, - 'touch-keyboard-active': touchKeyboardActive, - }); - - const poster = isPlaying - ? null - : html` <${VideoPoster} offlineImage=${logo} active=${streamOnline} /> `; - - // modal buttons - const notificationsButton = - notifications && - notifications.browser.enabled && - isPushNotificationSupported() && - html`<${NotifyButton} - serverName=${name} - onClick=${this.displayNotificationModal} - />`; - const externalActionButtons = html`
- ${externalActions && - externalActions.map( - function (action) { - return html`<${ExternalActionButton} - onClick=${this.displayExternalAction} - action=${action} - />`; - }.bind(this) - )} - - - ${federation.enabled && - html`<${FediverseFollowButton} - onClick=${this.displayFediverseFollowModal} - federationInfo=${federation} - serverName=${name} - />`} - ${notificationsButton} -
`; - - // modal component - const externalActionModal = - externalActionModalData && - html`<${ExternalActionModal} - action=${externalActionModalData} - onClose=${this.closeExternalActionModal} - />`; - - const fediverseFollowModal = - fediverseModalData && - html` - <${ExternalActionModal} - onClose=${this.closeFediverseFollowModal} - action=${fediverseModalData} - useIframe=${false} - customContent=${html`<${FediverseFollowModal} - name=${name} - logo=${logo} - federationInfo=${federation} - onClose=${this.closeFediverseFollowModal} - />`} - /> - `; - - const notificationModal = - notificationModalData && - html` <${ExternalActionModal} - onClose=${this.closeNotificationModal} - action=${notificationModalData} - useIframe=${false} - customContent=${html`<${NotifyModal} - notifications=${notifications} - streamName=${name} - accessToken=${accessToken} - />`} - />`; - - const authModal = - authModalData && - html` - <${ExternalActionModal} - onClose=${this.closeAuthModal} - action=${authModalData} - useIframe=${false} - customContent=${html`<${ChatSettingsModal} - name=${name} - logo=${logo} - onUsernameChange=${this.handleUsernameChange} - username=${username} - accessToken=${this.state.accessToken} - authenticated=${authenticated} - onClose=${this.closeAuthModal} - indieAuthEnabled=${indieAuthEnabled} - federationEnabled=${federation.enabled} - />`} - /> - `; - - const chat = this.state.websocket - ? html` - <${Chat} - websocket=${websocket} - username=${username} - authenticated=${authenticated} - chatInputEnabled=${chatInputEnabled && !chatDisabled} - instanceTitle=${name} - accessToken=${accessToken} - inputMaxBytes=${maxSocketPayloadSize - EST_SOCKET_PAYLOAD_BUFFER || - CHAT_MAX_MESSAGE_LENGTH} - /> - ` - : null; - - const TAB_CONTENT = [ - { - label: 'About', - content: html` -
-
-
- ${tagList && `#${tagList}`} -
-
-
- `, - }, - ]; - - if (federation.enabled) { - TAB_CONTENT.push({ - label: html`Followers - ${federation.followerCount > 10 - ? `${' '}(${federation.followerCount})` - : null}`, - content: html`<${Followers} />`, - }); - } - - const authIcon = '/img/user-settings.svg'; - - return html` -
- - -
-
-

- - - - ${streamOnline && streamTitle ? streamTitle : name} -

- - <${!chatDisabled && ChatMenu} - username=${username} - isModerator=${isModerator} - showAuthModal=${indieAuthEnabled && this.showAuthModal} - onUsernameChange=${this.handleUsernameChange} - onFocus=${this.handleFormFocus} - onBlur=${this.handleFormBlur} - chatDisabled=${chatDisabled} - noVideoContent=${noVideoContent} - handleChatPanelToggle=${this.handleChatPanelToggle} - /> -
-
- -
-
- - ${poster} -
- -
- ${streamStatusMessage} - ${viewerCountMessage} -
-
- -
- ${externalActionButtons && html`${externalActionButtons}`} - -
-
-
- -
- -
- -
-

- ${name} -

-

- ${streamOnline && streamTitle} -

- - -
- <${TabBar} tabs=${TAB_CONTENT} ariaLabel="User Content" /> -
-
-
-
- - - - ${chat} ${externalActionModal} ${fediverseFollowModal} - ${notificationModal} ${authModal} -
- `; - } -} diff --git a/webroot/js/chat/register.js b/webroot/js/chat/register.js deleted file mode 100644 index 53129046c..000000000 --- a/webroot/js/chat/register.js +++ /dev/null @@ -1,19 +0,0 @@ -import { URL_CHAT_REGISTRATION } from '../utils/constants.js'; - -export async function registerChat(username) { - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ displayName: username }), - }; - - try { - const response = await fetch(URL_CHAT_REGISTRATION, options); - const result = await response.json(); - return result; - } catch (e) { - console.error(e); - } -} diff --git a/webroot/js/components/auth-fediverse.js b/webroot/js/components/auth-fediverse.js deleted file mode 100644 index 7bda6c3b2..000000000 --- a/webroot/js/components/auth-fediverse.js +++ /dev/null @@ -1,206 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; - -const html = htm.bind(h); - -export default class FediverseAuth extends Component { - constructor(props) { - super(props); - - this.submitButtonPressed = this.submitButtonPressed.bind(this); - - this.state = { - account: '', - code: '', - errorMessage: null, - loading: false, - verifying: false, - valid: false, - }; - } - - async makeRequest(url, data) { - const rawResponse = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - const content = await rawResponse.json(); - if (content.message) { - this.setState({ errorMessage: content.message, loading: false }); - return; - } - } - - switchToCodeVerify() { - this.setState({ verifying: true, loading: false }); - } - - async validateCodeButtonPressed() { - const { accessToken } = this.props; - const { code } = this.state; - - this.setState({ loading: true, errorMessage: null }); - - const url = `/api/auth/fediverse/verify?accessToken=${accessToken}`; - const data = { code: code }; - - try { - await this.makeRequest(url, data); - - // Success. Reload the page. - window.location = '/'; - } catch (e) { - console.error(e); - this.setState({ errorMessage: e, loading: false }); - } - } - - async registerAccountButtonPressed() { - const { accessToken } = this.props; - const { account, valid } = this.state; - - if (!valid) { - return; - } - - const url = `/api/auth/fediverse?accessToken=${accessToken}`; - const normalizedAccount = account.replace(/^@+/, ''); - const data = { account: normalizedAccount }; - - this.setState({ loading: true, errorMessage: null }); - - try { - await this.makeRequest(url, data); - this.switchToCodeVerify(); - } catch (e) { - console.error(e); - this.setState({ errorMessage: e, loading: false }); - } - } - - async submitButtonPressed() { - const { verifying } = this.state; - if (verifying) { - this.validateCodeButtonPressed(); - } else { - this.registerAccountButtonPressed(); - } - } - - onInput = (e) => { - const { value } = e.target; - const { verifying } = this.state; - - if (verifying) { - this.setState({ code: value }); - return; - } - - const valid = validateAccount(value); - this.setState({ account: value, valid }); - }; - - render() { - const { errorMessage, account, code, valid, loading, verifying } = - this.state; - const { authenticated, username } = this.props; - const buttonState = valid ? '' : 'cursor-not-allowed opacity-50'; - - const loaderStyle = loading ? 'flex' : 'none'; - const message = verifying - ? 'Paste in the code that was sent to your Fediverse account. If you did not receive a code, make sure you can accept direct messages.' - : !authenticated - ? html`Receive a direct message on the Fediverse to ${' '} link your - account to ${' '} ${username}, or login - as a previously linked chat user.` - : html`You are already authenticated. However, you can add other - accounts or log in as a different user.`; - const label = verifying ? 'Code' : 'Your fediverse account'; - const placeholder = verifying ? '123456' : 'youraccount@fediverse.server'; - const buttonText = verifying ? 'Verify' : 'Authenticate with Fediverse'; - - const error = errorMessage - ? html` ` - : null; - - return html` -
-

${message}

- - ${error} - -
- - - -
- -

-

- - Learn more about using the Fediverse to authenticate with chat. - -
-

- You can link your chat identity with your Fediverse identity. - Next time you want to use this chat identity you can again go - through the Fediverse authentication. -

-
-
-

- -
- -

Authenticating.

-

Please wait...

-
-
- `; - } -} - -function validateAccount(account) { - account = account.replace(/^@+/, ''); - var regex = - /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return regex.test(String(account).toLowerCase()); -} diff --git a/webroot/js/components/auth-indieauth.js b/webroot/js/components/auth-indieauth.js deleted file mode 100644 index 74b93bfec..000000000 --- a/webroot/js/components/auth-indieauth.js +++ /dev/null @@ -1,187 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -export default class IndieAuthForm extends Component { - constructor(props) { - super(props); - - this.submitButtonPressed = this.submitButtonPressed.bind(this); - - this.state = { - errorMessage: null, - loading: false, - valid: false, - }; - } - - async submitButtonPressed() { - const { accessToken } = this.props; - const { host, valid } = this.state; - - if (!valid) { - return; - } - - const url = `/api/auth/indieauth?accessToken=${accessToken}`; - const data = { authHost: host }; - - this.setState({ loading: true }); - - try { - const rawResponse = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - const content = await rawResponse.json(); - if (content.message) { - this.setState({ errorMessage: content.message, loading: false }); - return; - } else if (!content.redirect) { - this.setState({ - errorMessage: 'Auth provider did not return a redirect URL.', - loading: false, - }); - return; - } - - if (content.redirect) { - const redirect = content.redirect; - window.location = redirect; - } - } catch (e) { - console.error(e); - this.setState({ errorMessage: e, loading: false }); - } - } - - onInput = (e) => { - const { value } = e.target; - let valid = validateURL(value); - this.setState({ host: value, valid }); - }; - - render() { - const { errorMessage, loading, host, valid } = this.state; - const { authenticated, username } = this.props; - const buttonState = valid ? '' : 'cursor-not-allowed opacity-50'; - const loaderStyle = loading ? 'flex' : 'none'; - - const message = !authenticated - ? html`Use your own domain to authenticate ${' '} - ${username} or login as a previously - ${' '} authenticated chat user using IndieAuth.` - : html`You are already authenticated. However, you can add other - domains or log in as a different user.`; - - let errorMessageText = errorMessage; - if (!!errorMessageText) { - if (errorMessageText.includes('url does not support indieauth')) { - errorMessageText = - 'The provided URL is either invalid or does not support IndieAuth.'; - } - } - - const error = errorMessage - ? html` ` - : null; - - return html`
-

${message}

- -

${error}

- -
- - - -
- -

-

- - Learn more about using IndieAuth to authenticate with chat. - -
-

- IndieAuth allows for a completely independent and decentralized - way of identifying yourself using your own domain. -

- -

- If you run an Owncast instance, you can use that domain here. - Otherwise, ${' '} - learn more about how you can support IndieAuth. -

-
-
-

- -
- -

Authenticating.

-

Please wait...

-
-
`; - } -} - -function validateURL(url) { - if (!url) { - return false; - } - - try { - const u = new URL(url); - if (!u) { - return false; - } - - if (u.protocol !== 'https:') { - return false; - } - } catch (e) { - return false; - } - - return true; -} diff --git a/webroot/js/components/auth-modal.js b/webroot/js/components/auth-modal.js deleted file mode 100644 index e9a37daee..000000000 --- a/webroot/js/components/auth-modal.js +++ /dev/null @@ -1,162 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import { ExternalActionButton } from './external-action-modal.js'; - -const html = htm.bind(h); - -export default class AuthModal extends Component { - constructor(props) { - super(props); - - this.submitButtonPressed = this.submitButtonPressed.bind(this); - - this.state = { - errorMessage: null, - loading: false, - valid: false, - }; - } - - async submitButtonPressed() { - const { accessToken } = this.props; - const { host, valid } = this.state; - - if (!valid) { - return; - } - - const url = `/api/auth/indieauth?accessToken=${accessToken}`; - const data = { authHost: host }; - - this.setState({ loading: true }); - - try { - const rawResponse = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - const content = await rawResponse.json(); - if (content.message) { - this.setState({ errorMessage: content.message, loading: false }); - return; - } else if (!content.redirect) { - this.setState({ - errorMessage: 'Auth provider did not return a redirect URL.', - loading: false, - }); - return; - } - - const redirect = content.redirect; - window.location = redirect; - } catch (e) { - console.error(e); - this.setState({ errorMessage: e, loading: false }); - } - } - - onInput = (e) => { - const { value } = e.target; - let valid = validateURL(value); - this.setState({ host: value, valid }); - }; - - render() { - const { errorMessage, host, valid, loading } = this.state; - const { authenticated } = this.props; - const buttonState = valid ? '' : 'cursor-not-allowed opacity-50'; - - const loaderStyle = loading ? 'flex' : 'none'; - - const message = !authenticated - ? `While you can chat completely anonymously you can also add - authentication so you can rejoin with the same chat persona from any - device or browser.` - : `You are already authenticated, however you can add other external sites or accounts to your chat account or log in as a different user.`; - - const error = errorMessage - ? html` ` - : null; - - return html` -
-

${message}

- - ${error} - -
- - - -

- Learn more about - IndieAuth. -

-
- -
- -

Authenticating.

-

Please wait...

-
-
- `; - } -} - -function validateURL(url) { - if (!url) { - return false; - } - - try { - const u = new URL(url); - if (!u) { - return false; - } - - if (u.protocol !== 'https:') { - return false; - } - } catch (e) { - return false; - } - - return true; -} diff --git a/webroot/js/components/chat-settings-modal.js b/webroot/js/components/chat-settings-modal.js deleted file mode 100644 index f07848570..000000000 --- a/webroot/js/components/chat-settings-modal.js +++ /dev/null @@ -1,73 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import TabBar from './tab-bar.js'; -import IndieAuthForm from './auth-indieauth.js'; -import FediverseAuth from './auth-fediverse.js'; - -const html = htm.bind(h); - -export default class ChatSettingsModal extends Component { - render() { - const { - accessToken, - authenticated, - federationEnabled, - username, - indieAuthEnabled, - } = this.props; - - const TAB_CONTENT = []; - if (indieAuthEnabled) { - TAB_CONTENT.push({ - label: html` - IndieAuth`, - content: html`<${IndieAuthForm}} - accessToken=${accessToken} - authenticated=${authenticated} - username=${username} - />`, - }); - } - - if (federationEnabled) { - TAB_CONTENT.push({ - label: html` - FediAuth`, - content: html`<${FediverseAuth}} - authenticated=${authenticated} - accessToken=${accessToken} - authenticated=${authenticated} - username=${username} - />`, - }); - } - - return html` -
- <${TabBar} tabs=${TAB_CONTENT} ariaLabel="Chat settings" /> -

- Note: This is for authentication purposes only, and no personal - information will be accessed or stored. -

-
- `; - } -} diff --git a/webroot/js/components/chat/chat-input.js b/webroot/js/components/chat/chat-input.js deleted file mode 100644 index dfccad32f..000000000 --- a/webroot/js/components/chat/chat-input.js +++ /dev/null @@ -1,400 +0,0 @@ -import { h, Component, createRef } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import { EmojiButton } from '/js/web_modules/@joeattardi/emoji-button.js'; - -import ContentEditable, { replaceCaret } from './content-editable.js'; -import { - generatePlaceholderText, - getCaretPosition, - convertToText, - convertOnPaste, - createEmojiMarkup, - trimNbsp, - emojify, -} from '../../utils/chat.js'; -import { - getLocalStorage, - setLocalStorage, - classNames, -} from '../../utils/helpers.js'; -import { - URL_CUSTOM_EMOJIS, - KEY_CHAT_FIRST_MESSAGE_SENT, - CHAT_CHAR_COUNT_BUFFER, - CHAT_OK_KEYCODES, - CHAT_KEY_MODIFIERS, -} from '../../utils/constants.js'; - -export default class ChatInput extends Component { - constructor(props, context) { - super(props, context); - this.formMessageInput = createRef(); - this.emojiPickerButton = createRef(); - - this.messageCharCount = 0; - - this.prepNewLine = false; - this.modifierKeyPressed = false; // control/meta/shift/alt - - this.state = { - inputHTML: '', - inputCharsLeft: props.inputMaxBytes, - hasSentFirstChatMessage: getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT), - emojiPicker: null, - emojiList: null, - emojiNames: null, - }; - - this.handleEmojiButtonClick = this.handleEmojiButtonClick.bind(this); - this.handleEmojiSelected = this.handleEmojiSelected.bind(this); - this.getCustomEmojis = this.getCustomEmojis.bind(this); - - this.handleMessageInputKeydown = this.handleMessageInputKeydown.bind(this); - this.handleMessageInputKeyup = this.handleMessageInputKeyup.bind(this); - this.handleMessageInputBlur = this.handleMessageInputBlur.bind(this); - this.handleSubmitChatButton = this.handleSubmitChatButton.bind(this); - this.handlePaste = this.handlePaste.bind(this); - - this.handleContentEditableChange = - this.handleContentEditableChange.bind(this); - } - - componentDidMount() { - this.getCustomEmojis(); - } - - getCustomEmojis() { - fetch(URL_CUSTOM_EMOJIS) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok ${response.ok}`); - } - return response.json(); - }) - .then((json) => { - const emojiList = json; - const emojiNames = emojiList.map((emoji) => emoji.name); - const emojiPicker = new EmojiButton({ - zIndex: 100, - theme: 'owncast', // see chat.css - custom: json, - initialCategory: 'custom', - showPreview: false, - autoHide: false, - autoFocusSearch: false, - showAnimation: false, - emojiSize: '24px', - position: 'right-start', - strategy: 'absolute', - }); - emojiPicker.on('emoji', (emoji) => { - this.handleEmojiSelected(emoji); - }); - emojiPicker.on('hidden', () => { - this.formMessageInput.current.focus(); - replaceCaret(this.formMessageInput.current); - }); - this.setState({ emojiNames, emojiList, emojiPicker }); - }) - .catch((error) => { - // this.handleNetworkingError(`Emoji Fetch: ${error}`); - }); - } - - handleEmojiButtonClick() { - const { emojiPicker } = this.state; - if (emojiPicker) { - emojiPicker.togglePicker(this.emojiPickerButton.current); - } - } - - handleEmojiSelected(emoji) { - const { inputHTML, inputCharsLeft } = this.state; - // if we're already at char limit, don't do anything - if (inputCharsLeft < 0) { - return; - } - let content = ''; - if (emoji.url) { - content = createEmojiMarkup(emoji, false); - } else { - content = emoji.emoji; - } - - const position = getCaretPosition(this.formMessageInput.current); - const newHTML = - inputHTML.substring(0, position) + - content + - inputHTML.substring(position); - - const charsLeft = this.calculateCurrentBytesLeft(newHTML); - this.setState({ - inputHTML: newHTML, - inputCharsLeft: charsLeft, - }); - // a hacky way add focus back into input field - setTimeout(() => { - const input = this.formMessageInput.current; - input.focus(); - replaceCaret(input); - }, 100); - } - - // autocomplete text from the given "list". "token" marks the start of word lookup. - autoComplete(token, list) { - const { inputHTML } = this.state; - const position = getCaretPosition(this.formMessageInput.current); - const at = inputHTML.lastIndexOf(token, position - 1); - if (at === -1) { - return false; - } - - let partial = inputHTML.substring(at + 1, position).trim(); - - if (this.partial === undefined) { - this.partial = []; - } - - if (partial === this.suggestion) { - partial = this.partial[token]; - } else { - this.partial[token] = partial; - } - - const possibilities = list.filter(function (item) { - return item.toLowerCase().startsWith(partial.toLowerCase()); - }); - - if (this.completionIndex === undefined) { - this.completionIndex = []; - } - - if ( - this.completionIndex[token] === undefined || - ++this.completionIndex[token] >= possibilities.length - ) { - this.completionIndex[token] = 0; - } - - if (possibilities.length > 0) { - this.suggestion = possibilities[this.completionIndex[token]]; - - const newHTML = - inputHTML.substring(0, at + 1) + - this.suggestion + - ' ' + - inputHTML.substring(position); - - this.setState({ - inputHTML: newHTML, - inputCharsLeft: this.calculateCurrentBytesLeft(newHTML), - }); - } - - return true; - } - - // replace :emoji: with the emoji - injectEmoji() { - const { inputHTML, emojiList } = this.state; - const textValue = convertToText(inputHTML); - const processedHTML = emojify(inputHTML, emojiList); - - if (textValue != convertToText(processedHTML)) { - this.setState({ - inputHTML: processedHTML, - }); - return true; - } - return false; - } - - handleMessageInputKeydown(event) { - const key = event && event.key; - - if (key === 'Enter') { - if (!this.prepNewLine) { - this.sendMessage(); - event.preventDefault(); - this.prepNewLine = false; - return; - } - } - // allow key presses such as command/shift/meta, etc even when message length is full later. - if (CHAT_KEY_MODIFIERS.includes(key)) { - this.modifierKeyPressed = true; - } - if (key === 'Control' || key === 'Shift') { - this.prepNewLine = true; - } - if (key === 'Tab') { - const { chatUserNames } = this.props; - const { emojiNames } = this.state; - if (this.autoComplete('@', chatUserNames)) { - event.preventDefault(); - } - if (this.autoComplete(':', emojiNames)) { - event.preventDefault(); - } - } - - // if new input pushes the potential chars over, don't do anything - const formField = this.formMessageInput.current; - const tempCharsLeft = this.calculateCurrentBytesLeft(formField.innerHTML); - if (tempCharsLeft <= 0 && !CHAT_OK_KEYCODES.includes(key)) { - if (!this.modifierKeyPressed) { - event.preventDefault(); // prevent typing more - } - return; - } - } - - handleMessageInputKeyup(event) { - const { key } = event; - if (key === 'Control' || key === 'Shift') { - this.prepNewLine = false; - } - if (CHAT_KEY_MODIFIERS.includes(key)) { - this.modifierKeyPressed = false; - } - - if (key === ':' || key === ';') { - this.injectEmoji(); - } - } - - handleMessageInputBlur() { - this.prepNewLine = false; - this.modifierKeyPressed = false; - } - - handlePaste(event) { - // don't allow paste if too much text already - if (this.state.inputCharsLeft < 0) { - event.preventDefault(); - return; - } - convertOnPaste(event, this.state.emojiList); - this.handleMessageInputKeydown(event); - } - - handleSubmitChatButton(event) { - event.preventDefault(); - this.sendMessage(); - } - - sendMessage() { - const { handleSendMessage, inputMaxBytes } = this.props; - const { hasSentFirstChatMessage, inputHTML, inputCharsLeft } = this.state; - if (inputCharsLeft < 0) { - return; - } - const message = convertToText(inputHTML); - const newStates = { - inputHTML: '', - inputCharsLeft: inputMaxBytes, - }; - - handleSendMessage(message); - - if (!hasSentFirstChatMessage) { - newStates.hasSentFirstChatMessage = true; - setLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT, true); - } - - // clear things out. - this.setState(newStates); - } - - handleContentEditableChange(event) { - const value = event.target.value; - this.setState({ - inputHTML: value, - inputCharsLeft: this.calculateCurrentBytesLeft(value), - }); - } - - calculateCurrentBytesLeft(inputContent) { - const { inputMaxBytes } = this.props; - const curBytes = new Blob([trimNbsp(inputContent)]).size; - return inputMaxBytes - curBytes; - } - - render(props, state) { - const { hasSentFirstChatMessage, inputCharsLeft, inputHTML, emojiPicker } = - state; - const { inputEnabled, inputMaxBytes } = props; - const emojiButtonStyle = { - display: emojiPicker && inputCharsLeft > 0 ? 'block' : 'none', - }; - const extraClasses = classNames({ - 'display-count': inputCharsLeft <= CHAT_CHAR_COUNT_BUFFER, - }); - const placeholderText = generatePlaceholderText( - inputEnabled, - hasSentFirstChatMessage - ); - return html` -
-
- <${ContentEditable} - id="message-input" - aria-role="textbox" - class="appearance-none block w-full bg-transparent text-sm text-gray-700 h-full focus:outline-none" - aria-placeholder=${placeholderText} - innerRef=${this.formMessageInput} - html=${inputHTML} - disabled=${!inputEnabled} - onChange=${this.handleContentEditableChange} - onKeyDown=${this.handleMessageInputKeydown} - onKeyUp=${this.handleMessageInputKeyup} - onBlur=${this.handleMessageInputBlur} - onPaste=${this.handlePaste} - /> -
-
- - - - - - - ${inputCharsLeft} bytes -
-
- `; - } -} diff --git a/webroot/js/components/chat/chat-menu.js b/webroot/js/components/chat/chat-menu.js deleted file mode 100644 index 30f4e9fa3..000000000 --- a/webroot/js/components/chat/chat-menu.js +++ /dev/null @@ -1,140 +0,0 @@ -import { h, createContext } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import { useState, useEffect, useRef } from '/js/web_modules/preact/hooks.js'; -import UsernameForm from './username.js'; -import { ChatIcon, UserIcon, CaretDownIcon, AuthIcon } from '../icons/index.js'; - -const html = htm.bind(h); - -const moderatorFlag = html` - -`; - -const Context = createContext(); - -export const ChatMenu = (props) => { - const { - username, - isModerator, - chatDisabled, - noVideoContent, - handleChatPanelToggle, - onUsernameChange, - showAuthModal, - onFocus, - onBlur, - } = props; - - const [chatMenuOpen, setChatMenuOpen] = useState(false); - const [view, setView] = useState('main'); - - const chatMenuRef = useRef(undefined); - closeOnOutsideClick(chatMenuRef, setChatMenuOpen); - - useEffect(() => { - if (chatMenuOpen) setView('main'); - }, [chatMenuOpen]); - - const authMenuItem = - showAuthModal && - html`
  • - -
  • `; - - return html` - <${Context.Provider} value=${props}> -
    - - ${ - chatMenuOpen && - html`
    - ${view === 'main' && - html``} - ${view != 'main' && - html`<${SubMenuView} view=${view} setView=${setView} />`} -
    ` - } -
    - `; -}; - -const SubMenuView = ({ view, setView }) => { - return html` -
    - - ${view === 'change_username' && html`<${ChangeUsernameView} />`} -
    - `; -}; - -function closeOnOutsideClick(ref, setter) { - useEffect(() => { - function handleClickOutside(event) { - if (ref.current && !ref.current.contains(event.target)) { - setter(undefined); - } - } - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [ref]); -} diff --git a/webroot/js/components/chat/chat-message-view.js b/webroot/js/components/chat/chat-message-view.js deleted file mode 100644 index 94e25cc85..000000000 --- a/webroot/js/components/chat/chat-message-view.js +++ /dev/null @@ -1,229 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import Mark from '/js/web_modules/markjs/dist/mark.es6.min.js'; -const html = htm.bind(h); - -import { - messageBubbleColorForHue, - textColorForHue, -} from '../../utils/user-colors.js'; -import { convertToText, checkIsModerator } from '../../utils/chat.js'; -import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js'; -import { getDiffInDaysFromNow } from '../../utils/helpers.js'; -import ModeratorActions from './moderator-actions.js'; - -export default class ChatMessageView extends Component { - constructor(props) { - super(props); - this.state = { - formattedMessage: '', - moderatorMenuOpen: false, - }; - } - - shouldComponentUpdate(nextProps, nextState) { - const { formattedMessage } = this.state; - const { formattedMessage: nextFormattedMessage } = nextState; - - return ( - formattedMessage !== nextFormattedMessage || - (!this.props.isModerator && nextProps.isModerator) - ); - } - - async componentDidMount() { - const { message, username } = this.props; - const { body } = message; - - if (message && username) { - const formattedMessage = await formatMessageText(body, username); - this.setState({ - formattedMessage, - }); - } - } - render() { - const { message, isModerator, accessToken } = this.props; - const { user, timestamp } = message; - - if (!user) { - return null; - } - - const { displayName, displayColor, createdAt, isBot, authenticated } = user; - const isAuthorModerator = checkIsModerator(message); - - const isMessageModeratable = - isModerator && message.type === SOCKET_MESSAGE_TYPES.CHAT; - - const { formattedMessage } = this.state; - if (!formattedMessage) { - return null; - } - const formattedTimestamp = `Sent at ${formatTimestamp(timestamp)}`; - const userMetadata = createdAt - ? `${displayName} first joined ${formatTimestamp(createdAt)}` - : null; - - const isSystemMessage = message.type === SOCKET_MESSAGE_TYPES.SYSTEM; - - const authorTextColor = isSystemMessage - ? { color: '#fff' } - : { color: textColorForHue(displayColor) }; - const backgroundStyle = isSystemMessage - ? { backgroundColor: '#667eea' } - : { backgroundColor: messageBubbleColorForHue(displayColor) }; - const messageClassString = isSystemMessage - ? 'message flex flex-row items-start p-4 m-2 rounded-lg shadow-l border-solid border-indigo-700 border-2 border-opacity-60 text-l' - : `message relative flex flex-row items-start p-3 m-3 rounded-lg shadow-s text-sm ${ - isMessageModeratable ? 'moderatable' : '' - }`; - - const isModeratorFlair = isAuthorModerator - ? html`` - : null; - - const isBotFlair = isBot - ? html`` - : null; - - const authorAuthenticatedFlair = authenticated - ? html`` - : null; - - return html` -
    -
    -
    - ${isBotFlair} ${authorAuthenticatedFlair} ${isModeratorFlair} - ${displayName} -
    - ${isMessageModeratable && - html`<${ModeratorActions} - message=${message} - accessToken=${accessToken} - />`} -
    -
    -
    - `; - } -} - -export async function formatMessageText(message, username) { - let formattedText = getMessageWithEmbeds(message); - formattedText = convertToMarkup(formattedText); - return await highlightUsername(formattedText, username); -} - -function highlightUsername(message, username) { - // https://github.com/julmot/mark.js/issues/115 - const node = document.createElement('span'); - node.innerHTML = message; - return new Promise((res) => { - new Mark(node).mark(username, { - element: 'span', - className: 'highlighted px-1 rounded font-bold bg-orange-500', - separateWordSearch: false, - accuracy: { - value: 'exactly', - limiters: [',', '.', "'", '?', '@'], - }, - done() { - res(node.innerHTML); - }, - }); - }); -} - -function getMessageWithEmbeds(message) { - var embedText = ''; - // Make a temporary element so we can actually parse the html and pull anchor tags from it. - // This is a better approach than regex. - var container = document.createElement('p'); - container.innerHTML = message; - - var anchors = container.getElementsByTagName('a'); - for (var i = 0; i < anchors.length; i++) { - const url = anchors[i].href; - if (url.indexOf('instagram.com/p/') > -1) { - embedText += getInstagramEmbedFromURL(url); - } - } - - // If this message only consists of a single embeddable link - // then only return the embed and strip the link url from the text. - if ( - embedText !== '' && - anchors.length == 1 && - isMessageJustAnchor(message, anchors[0]) - ) { - return embedText; - } - return message + embedText; -} - -function getInstagramEmbedFromURL(url) { - const urlObject = new URL(url.replace(/\/$/, '')); - urlObject.pathname += '/embed'; - return ``; -} - -function isMessageJustAnchor(message, anchor) { - return stripTags(message) === stripTags(anchor.innerHTML); -} - -function formatTimestamp(sentAt) { - sentAt = new Date(sentAt); - if (isNaN(sentAt)) { - return ''; - } - - let diffInDays = getDiffInDaysFromNow(sentAt); - if (diffInDays >= 1) { - return ( - `at ${sentAt.toLocaleDateString('en-US', { - dateStyle: 'medium', - })} at ` + sentAt.toLocaleTimeString() - ); - } - - return `${sentAt.toLocaleTimeString()}`; -} - -/* - You would call this when receiving a plain text - value back from an API, and before inserting the - text into the `contenteditable` area on a page. -*/ -function convertToMarkup(str = '') { - return convertToText(str).replace(/\n/g, '

    '); -} - -function stripTags(str) { - return str.replace(/<\/?[^>]+(>|$)/g, ''); -} diff --git a/webroot/js/components/chat/chat.js b/webroot/js/components/chat/chat.js deleted file mode 100644 index f5f523af4..000000000 --- a/webroot/js/components/chat/chat.js +++ /dev/null @@ -1,508 +0,0 @@ -import { h, Component, createRef } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import Message from './message.js'; -import ChatInput from './chat-input.js'; -import { CALLBACKS, SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js'; -import { jumpToBottom, debounce } from '../../utils/helpers.js'; -import { - extraUserNamesFromMessageHistory, - checkIsModerator, -} from '../../utils/chat.js'; -import { - URL_CHAT_HISTORY, - MESSAGE_JUMPTOBOTTOM_BUFFER, -} from '../../utils/constants.js'; - -const MAX_RENDER_BACKLOG = 300; - -// Add message types that should be displayed in chat to this array. -const renderableChatStyleMessages = [ - SOCKET_MESSAGE_TYPES.NAME_CHANGE, - SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO, - SOCKET_MESSAGE_TYPES.USER_JOINED, - SOCKET_MESSAGE_TYPES.CHAT_ACTION, - SOCKET_MESSAGE_TYPES.SYSTEM, - SOCKET_MESSAGE_TYPES.CHAT, - SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_FOLLOW, - SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_LIKE, - SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_REPOST, -]; -export default class Chat extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - chatUserNames: [], - // Ordered array of messages sorted by timestamp. - sortedMessages: [], - - newMessagesReceived: false, - webSocketConnected: true, - isModerator: false, - }; - - this.scrollableMessagesContainer = createRef(); - - this.websocket = null; - this.receivedFirstMessages = false; - this.receivedMessageUpdate = false; - this.hasFetchedHistory = false; - - // Unordered dictionary of messages keyed by ID. - this.messages = {}; - - this.windowBlurred = false; - this.numMessagesSinceBlur = 0; - - this.getChatHistory = this.getChatHistory.bind(this); - this.handleNetworkingError = this.handleNetworkingError.bind(this); - this.handleWindowBlur = this.handleWindowBlur.bind(this); - this.handleWindowFocus = this.handleWindowFocus.bind(this); - this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 500); - this.messageListCallback = this.messageListCallback.bind(this); - this.receivedWebsocketMessage = this.receivedWebsocketMessage.bind(this); - this.scrollToBottom = this.scrollToBottom.bind(this); - this.submitChat = this.submitChat.bind(this); - this.websocketConnected = this.websocketConnected.bind(this); - this.websocketDisconnected = this.websocketDisconnected.bind(this); - } - - componentDidMount() { - this.setupWebSocketCallbacks(); - - window.addEventListener('resize', this.handleWindowResize); - - if (!this.props.readonly) { - window.addEventListener('blur', this.handleWindowBlur); - window.addEventListener('focus', this.handleWindowFocus); - } - - this.messageListObserver = new MutationObserver(this.messageListCallback); - this.messageListObserver.observe(this.scrollableMessagesContainer.current, { - childList: true, - }); - } - - shouldComponentUpdate(nextProps, nextState) { - const { username, chatInputEnabled } = this.props; - const { username: nextUserName, chatInputEnabled: nextChatEnabled } = - nextProps; - - const { - webSocketConnected, - chatUserNames, - newMessagesReceived, - sortedMessages, - } = this.state; - - const { - webSocketConnected: nextSocket, - chatUserNames: nextUserNames, - newMessagesReceived: nextMessagesReceived, - } = nextState; - - // If there are an updated number of sorted message then a render pass - // needs to take place to render these new messages. - if ( - Object.keys(sortedMessages).length !== - Object.keys(nextState.sortedMessages).length - ) { - return true; - } - - if (newMessagesReceived) { - return true; - } - - return ( - username !== nextUserName || - chatInputEnabled !== nextChatEnabled || - webSocketConnected !== nextSocket || - chatUserNames.length !== nextUserNames.length || - newMessagesReceived !== nextMessagesReceived - ); - } - - componentDidUpdate(prevProps, prevState) { - const { accessToken } = this.props; - - // Fetch chat history - if (!this.hasFetchedHistory && accessToken) { - this.hasFetchedHistory = true; - this.getChatHistory(accessToken); - } - } - - componentWillUnmount() { - window.removeEventListener('resize', this.handleWindowResize); - if (!this.props.readonly) { - window.removeEventListener('blur', this.handleWindowBlur); - window.removeEventListener('focus', this.handleWindowFocus); - } - this.messageListObserver.disconnect(); - } - - setupWebSocketCallbacks() { - this.websocket = this.props.websocket; - if (this.websocket) { - this.websocket.addListener( - CALLBACKS.RAW_WEBSOCKET_MESSAGE_RECEIVED, - this.receivedWebsocketMessage - ); - this.websocket.addListener( - CALLBACKS.WEBSOCKET_CONNECTED, - this.websocketConnected - ); - this.websocket.addListener( - CALLBACKS.WEBSOCKET_DISCONNECTED, - this.websocketDisconnected - ); - } - } - - // fetch chat history - async getChatHistory(accessToken) { - const { username } = this.props; - try { - const response = await fetch( - URL_CHAT_HISTORY + `?accessToken=${accessToken}` - ); - const data = await response.json(); - - // Backlog of usernames from history - const allChatUserNames = extraUserNamesFromMessageHistory(data); - const chatUserNames = allChatUserNames.filter((name) => name != username); - - this.addNewRenderableMessages(data); - - this.setState((previousState) => { - return { - ...previousState, - chatUserNames, - }; - }); - } catch (error) { - this.handleNetworkingError(`Fetch getChatHistory: ${error}`); - } - - jumpToBottom(this.scrollableMessagesContainer.current, 'instant'); - } - - receivedWebsocketMessage(message) { - this.handleMessage(message); - } - - handleNetworkingError(error) { - // todo: something more useful - console.error('chat error', error); - } - - // Give a list of message IDs and the visibility state they should change to. - updateMessagesVisibility(idsToUpdate, visible) { - let messageList = { ...this.messages }; - - // Iterate through each ID and mark the associated ID in our messages - // dictionary with the new visibility. - for (const id of idsToUpdate) { - const message = messageList[id]; - if (message) { - message.visible = visible; - messageList[id] = message; - } - } - - const updatedMessagesList = { - ...this.messages, - ...messageList, - }; - - this.messages = updatedMessagesList; - - this.resortAndRenderMessages(); - } - - handleChangeModeratorStatus(isModerator) { - if (isModerator !== this.state.isModerator) { - this.setState((previousState) => { - return { ...previousState, isModerator: isModerator }; - }); - } - } - - handleWindowFocusNotificationCount(readonly, messageType) { - // if window is blurred and we get a new message, add 1 to title - if ( - !readonly && - messageType === SOCKET_MESSAGE_TYPES.CHAT && - this.windowBlurred - ) { - this.numMessagesSinceBlur += 1; - } - } - - addNewRenderableMessages(messagesArray) { - // Convert the array of chat history messages into an object - // to be merged with the existing chat messages. - const newMessages = messagesArray.reduce( - (o, message) => ({ ...o, [message.id]: message }), - {} - ); - - // Keep our unsorted collection of messages keyed by ID. - const updatedMessagesList = { - ...newMessages, - ...this.messages, - }; - this.messages = updatedMessagesList; - - this.resortAndRenderMessages(); - } - - resortAndRenderMessages() { - // Convert the unordered dictionary of messages to an ordered array. - // NOTE: This sorts the entire collection of messages on every new message - // because the order a message comes in cannot be trusted that it's the order - // it was sent, you need to sort by timestamp. I don't know if there - // is a performance problem waiting to occur here for larger chat feeds. - var sortedMessages = Object.values(this.messages) - // Filter out messages set to not be visible - .filter((message) => message.visible !== false) - .sort((a, b) => { - return Date.parse(a.timestamp) - Date.parse(b.timestamp); - }); - - // Cap this list to 300 items to improve browser performance. - if (sortedMessages.length >= MAX_RENDER_BACKLOG) { - sortedMessages = sortedMessages.slice( - sortedMessages.length - MAX_RENDER_BACKLOG - ); - } - - this.setState((previousState) => { - return { - ...previousState, - newMessagesReceived: true, - sortedMessages, - }; - }); - } - - // handle any incoming message - handleMessage(message) { - const { type: messageType } = message; - const { readonly, username } = this.props; - - // Allow non-user chat messages to be visible by default. - const messageVisible = - message.visible || messageType !== SOCKET_MESSAGE_TYPES.CHAT; - - // Show moderator status - if (messageType === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { - const modStatusUpdate = checkIsModerator(message); - this.handleChangeModeratorStatus(modStatusUpdate); - } - - // Change the visibility of messages by ID. - if (messageType === SOCKET_MESSAGE_TYPES.VISIBILITY_UPDATE) { - const idsToUpdate = message.ids; - const visible = message.visible; - this.updateMessagesVisibility(idsToUpdate, visible); - } else if ( - renderableChatStyleMessages.includes(messageType) && - messageVisible - ) { - // Add new message to the chat feed. - this.addNewRenderableMessages([message]); - - // Update the usernames list, filtering out our own name. - const updatedAllChatUserNames = this.updateAuthorList(message); - if (updatedAllChatUserNames.length) { - const updatedChatUserNames = updatedAllChatUserNames.filter( - (name) => name != username - ); - this.setState((previousState) => { - return { - ...previousState, - chatUserNames: [...updatedChatUserNames], - }; - }); - } - } - - // Update the window title if needed. - this.handleWindowFocusNotificationCount(readonly, messageType); - } - - websocketConnected() { - this.setState((previousState) => { - return { - ...previousState, - webSocketConnected: true, - }; - }); - } - - websocketDisconnected() { - this.setState((previousState) => { - return { - ...previousState, - webSocketConnected: false, - }; - }); - } - - submitChat(content) { - if (!content) { - return; - } - const message = { - body: content, - type: SOCKET_MESSAGE_TYPES.CHAT, - }; - this.websocket.send(message); - } - - updateAuthorList(message) { - const { type } = message; - let nameList = this.state.chatUserNames; - - if ( - type === SOCKET_MESSAGE_TYPES.CHAT && - !nameList.includes(message.user.displayName) - ) { - nameList.push(message.user.displayName); - return nameList; - } else if (type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) { - const { oldName, user } = message; - const oldNameIndex = nameList.indexOf(oldName); - nameList.splice(oldNameIndex, 1, user.displayName); - return nameList; - } - - return []; - } - - scrollToBottom() { - jumpToBottom(this.scrollableMessagesContainer.current); - } - - checkShouldScroll() { - const { scrollTop, scrollHeight, clientHeight } = - this.scrollableMessagesContainer.current; - const fullyScrolled = scrollHeight - clientHeight; - const shouldScroll = - scrollHeight >= clientHeight && - fullyScrolled - scrollTop < MESSAGE_JUMPTOBOTTOM_BUFFER; - - return shouldScroll; - } - - handleWindowResize() { - this.scrollToBottom(); - } - - handleWindowBlur() { - this.windowBlurred = true; - } - - handleWindowFocus() { - this.windowBlurred = false; - this.numMessagesSinceBlur = 0; - window.document.title = this.props.instanceTitle; - } - - // if the messages list grows in number of child message nodes due to new messages received, scroll to bottom. - messageListCallback(mutations) { - const numMutations = mutations.length; - - if (numMutations) { - const item = mutations[numMutations - 1]; - if (item.type === 'childList' && item.addedNodes.length) { - if (this.state.newMessagesReceived) { - if (!this.receivedFirstMessages) { - this.scrollToBottom(); - this.receivedFirstMessages = true; - } else if (this.checkShouldScroll()) { - this.scrollToBottom(); - } - - this.setState((previousState) => { - return { - ...previousState, - newMessagesReceived: false, - }; - }); - } - } - // update document title if window blurred - if ( - this.numMessagesSinceBlur && - !this.props.readonly && - this.windowBlurred - ) { - this.updateDocumentTitle(); - } - } - } - - updateDocumentTitle() { - const num = - this.numMessagesSinceBlur > 10 ? '10+' : this.numMessagesSinceBlur; - window.document.title = `${num} 💬 :: ${this.props.instanceTitle}`; - } - - render(props, state) { - const { username, readonly, chatInputEnabled, inputMaxBytes, accessToken } = - props; - const { sortedMessages, chatUserNames, webSocketConnected, isModerator } = - state; - - const messageList = sortedMessages.map( - (message) => - html`<${Message} - message=${message} - username=${username} - key=${message.id} - isModerator=${isModerator} - accessToken=${accessToken} - />` - ); - - if (readonly) { - return html` -
    - ${messageList} -
    - `; - } - - return html` -
    -
    -
    - ${messageList} -
    - <${ChatInput} - chatUserNames=${chatUserNames} - inputEnabled=${webSocketConnected && chatInputEnabled} - handleSendMessage=${this.submitChat} - inputMaxBytes=${inputMaxBytes} - /> -
    -
    - `; - } -} diff --git a/webroot/js/components/chat/content-editable.js b/webroot/js/components/chat/content-editable.js deleted file mode 100644 index fa81bea00..000000000 --- a/webroot/js/components/chat/content-editable.js +++ /dev/null @@ -1,129 +0,0 @@ -/* -Since we can't really import react-contenteditable here, I'm borrowing code for this component from here: -github.com/lovasoa/react-contenteditable/ - -and here: -https://stackoverflow.com/questions/22677931/react-js-onchange-event-for-contenteditable/27255103#27255103 - -*/ -import { h, Component, createRef } from '/js/web_modules/preact.js'; - -export function replaceCaret(el) { - // Place the caret at the end of the element - const target = document.createTextNode(''); - el.appendChild(target); - // do not move caret if element was not focused - const isTargetFocused = document.activeElement === el; - if (target !== null && target.nodeValue !== null && isTargetFocused) { - var sel = window.getSelection(); - if (sel !== null) { - var range = document.createRange(); - range.setStart(target, target.nodeValue.length); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); - } - if (el) el.focus(); - } -} - -function normalizeHtml(str) { - return str && str.replace(/ |\u202F|\u00A0/g, ' '); -} - - - -export default class ContentEditable extends Component { - constructor(props) { - super(props); - - this.el = createRef(); - - this.lastHtml = ''; - - this.emitChange = this.emitChange.bind(this); - this.getDOMElement = this.getDOMElement.bind(this); - } - - shouldComponentUpdate(nextProps) { - const { props } = this; - const el = this.getDOMElement(); - - // We need not rerender if the change of props simply reflects the user's edits. - // Rerendering in this case would make the cursor/caret jump - - // Rerender if there is no element yet... (somehow?) - if (!el) return true; - - // ...or if html really changed... (programmatically, not by user edit) - if ( - normalizeHtml(nextProps.html) !== normalizeHtml(el.innerHTML) - ) { - return true; - } - - // Handle additional properties - return props.disabled !== nextProps.disabled || - props.tagName !== nextProps.tagName || - props.className !== nextProps.className || - props.innerRef !== nextProps.innerRef; - } - - componentDidUpdate() { - const el = this.getDOMElement(); - if (!el) return; - - // Perhaps React (whose VDOM gets outdated because we often prevent - // rerendering) did not update the DOM. So we update it manually now. - if (this.props.html !== el.innerHTML) { - el.innerHTML = this.props.html; - } - this.lastHtml = this.props.html; - replaceCaret(el); - } - - getDOMElement() { - return (this.props.innerRef && typeof this.props.innerRef !== 'function' ? this.props.innerRef : this.el).current; - } - - - emitChange(originalEvt) { - const el = this.getDOMElement(); - if (!el) return; - - const html = el.innerHTML; - if (this.props.onChange && html !== this.lastHtml) { - // Clone event with Object.assign to avoid - // "Cannot assign to read only property 'target' of object" - const evt = Object.assign({}, originalEvt, { - target: { - value: html - } - }); - this.props.onChange(evt); - } - this.lastHtml = html; - } - - render(props) { - const { html, innerRef } = props; - return h( - 'div', - { - ...props, - ref: typeof innerRef === 'function' ? (current) => { - innerRef(current) - this.el.current = current - } : innerRef || this.el, - onInput: this.emitChange, - onFocus: this.props.onFocus || this.emitChange, - onBlur: this.props.onBlur || this.emitChange, - onKeyup: this.props.onKeyUp || this.emitChange, - onKeydown: this.props.onKeyDown || this.emitChange, - contentEditable: !this.props.disabled, - dangerouslySetInnerHTML: { __html: html }, - }, - this.props.children, - ); - } -} diff --git a/webroot/js/components/chat/message.js b/webroot/js/components/chat/message.js deleted file mode 100644 index ae35a2367..000000000 --- a/webroot/js/components/chat/message.js +++ /dev/null @@ -1,141 +0,0 @@ -import { h } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import ChatMessageView from './chat-message-view.js'; - -import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js'; -import { checkIsModerator } from '../../utils/chat.js'; - -function SystemMessage(props) { - const { contents } = props; - return html` -
    -
    -
    - ${contents} -
    -
    -
    - `; -} - -function SingleFederatedUser(props) { - const { message } = props; - const { type, body, title, image, link } = message; - - let icon = null; - switch (type) { - case SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_FOLLOW: - icon = '/img/follow.svg'; - break; - case SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_LIKE: - icon = '/img/like.svg'; - break; - case SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_REPOST: - icon = '/img/repost.svg'; - break; - default: - break; - } - - return html` - -
    -
    - - -
    -
    -
    - ${title} -
    -

    -
    -
    -
    - `; -} - -export default function Message(props) { - const { message } = props; - const { type, oldName, user, body } = message; - if ( - type === SOCKET_MESSAGE_TYPES.CHAT || - type === SOCKET_MESSAGE_TYPES.SYSTEM - ) { - return html`<${ChatMessageView} ...${props} />`; - } else if (type === SOCKET_MESSAGE_TYPES.NAME_CHANGE) { - // User changed their name - const { displayName } = user; - const contents = html` -
    - ${oldName} is now known as ${' '} - ${displayName}. -
    - `; - return html`<${SystemMessage} contents=${contents} />`; - } else if (type === SOCKET_MESSAGE_TYPES.USER_JOINED) { - const { displayName, isBot } = user; - const isAuthorModerator = checkIsModerator(message); - const messageAuthorFlair = isAuthorModerator - ? html`` - : null; - - const contents = html`
    - ${messageAuthorFlair}${displayName} - ${' '}joined the chat. -
    `; - return html`<${SystemMessage} contents=${contents} />`; - } else if (type === SOCKET_MESSAGE_TYPES.CHAT_ACTION) { - const contents = html``; - return html`<${SystemMessage} contents=${contents} />`; - } else if (type === SOCKET_MESSAGE_TYPES.CONNECTED_USER_INFO) { - // moderator message - const isModerator = checkIsModerator(message); - if (isModerator) { - const contents = html`
    - You are now a - moderator. -
    `; - return html`<${SystemMessage} contents=${contents} />`; - } - } else if ( - type === SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_FOLLOW || - SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_LIKE || - SOCKET_MESSAGE_TYPES.FEDIVERSE_ENGAGEMENT_REPOST - ) { - return html` <${SingleFederatedUser} message=${message} /> `; - } else { - console.log('Unknown message type:', type); - } -} diff --git a/webroot/js/components/chat/moderator-actions.js b/webroot/js/components/chat/moderator-actions.js deleted file mode 100644 index d90939e33..000000000 --- a/webroot/js/components/chat/moderator-actions.js +++ /dev/null @@ -1,298 +0,0 @@ -import { h, Component, createRef } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import { textColorForHue } from '../../utils/user-colors.js'; -import { URL_BAN_USER, URL_HIDE_MESSAGE } from '../../utils/constants.js'; - -const html = htm.bind(h); - -const HIDE_MESSAGE_ICON = `/img/hide-message-grey.svg`; -const HIDE_MESSAGE_ICON_HOVER = '/img/hide-message.svg'; -const BAN_USER_ICON = '/img/ban-user-grey.svg'; -const BAN_USER_ICON_HOVER = '/img/ban-user.svg'; - -export default class ModeratorActions extends Component { - constructor(props) { - super(props); - this.state = { - isMenuOpen: false, - }; - this.handleOpenMenu = this.handleOpenMenu.bind(this); - this.handleCloseMenu = this.handleCloseMenu.bind(this); - } - - handleOpenMenu() { - this.setState({ - isMenuOpen: true, - }); - } - - handleCloseMenu() { - this.setState({ - isMenuOpen: false, - }); - } - - render() { - const { isMenuOpen } = this.state; - const { message, accessToken } = this.props; - const { id } = message; - const { user } = message; - - return html` -
    - - - ${isMenuOpen && - html`<${ModeratorMenu} - message=${message} - onDismiss=${this.handleCloseMenu} - accessToken=${accessToken} - id=${id} - userId=${user.id} - />`} -
    - `; - } -} - -class ModeratorMenu extends Component { - constructor(props) { - super(props); - this.menuNode = createRef(); - - this.state = { - displayMoreInfo: false, - }; - this.handleClickOutside = this.handleClickOutside.bind(this); - this.handleToggleMoreInfo = this.handleToggleMoreInfo.bind(this); - this.handleBanUser = this.handleBanUser.bind(this); - this.handleHideMessage = this.handleHideMessage.bind(this); - } - - componentDidMount() { - document.addEventListener('mousedown', this.handleClickOutside, false); - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.handleClickOutside, false); - } - - handleClickOutside = (e) => { - if ( - this.menuNode && - !this.menuNode.current.contains(e.target) && - this.props.onDismiss - ) { - this.props.onDismiss(); - } - }; - - handleToggleMoreInfo() { - this.setState({ - displayMoreInfo: !this.state.displayMoreInfo, - }); - } - - async handleHideMessage() { - if (!confirm('Are you sure you want to remove this message from chat?')) { - this.props.onDismiss(); - return; - } - - const { accessToken, id } = this.props; - const url = new URL(location.origin + URL_HIDE_MESSAGE); - url.searchParams.append('accessToken', accessToken); - const hideMessageUrl = url.toString(); - - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ idArray: [id] }), - }; - - try { - await fetch(hideMessageUrl, options); - } catch (e) { - console.error(e); - } - - this.props.onDismiss(); - } - - async handleBanUser() { - if (!confirm('Are you sure you want to remove this user from chat?')) { - this.props.onDismiss(); - return; - } - - const { accessToken, userId } = this.props; - const url = new URL(location.origin + URL_BAN_USER); - url.searchParams.append('accessToken', accessToken); - const hideMessageUrl = url.toString(); - - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ userId: userId }), - }; - - try { - await fetch(hideMessageUrl, options); - } catch (e) { - console.error(e); - } - - this.props.onDismiss(); - } - - render() { - const { message } = this.props; - const { displayMoreInfo } = this.state; - return html` - - `; - } -} - -// 3 dots button -function ModeratorMenuItem({ icon, hoverIcon, label, onClick }) { - return html` - - `; -} - -// more details panel that display message, prev usernames, actions -function ModeratorMoreInfoContainer({ - message, - handleHideMessage, - handleBanUser, -}) { - const { user, timestamp, body } = message; - const { displayName, createdAt, previousNames, displayColor } = user; - const isAuthorModerator = user.scopes && user.scopes.includes('MODERATOR'); - - const authorTextColor = { color: textColorForHue(displayColor) }; - const createDate = new Date(createdAt); - const sentDate = new Date(timestamp); - return html` -
    -
    -

    - Sent at ${sentDate.toLocaleTimeString()} -

    -
    -
    -
    -

    Sent by:

    -

    - ${displayName} -

    - -

    - First joined: ${createDate.toLocaleString()} -

    - - ${previousNames.length > 1 && - html` -

    - Previously known as: ${' '} - ${previousNames.join(', ')} -

    - `} -
    -
    - <${handleHideMessage && ModeratorMenuItem} - icon=${HIDE_MESSAGE_ICON} - hoverIcon=${HIDE_MESSAGE_ICON_HOVER} - label="Hide message" - onClick="${handleHideMessage}" - /> - <${handleBanUser && ModeratorMenuItem} - icon=${BAN_USER_ICON} - hoverIcon=${BAN_USER_ICON_HOVER} - label="Ban user" - onClick="${handleBanUser}" - /> -
    -
    - `; -} diff --git a/webroot/js/components/chat/username.js b/webroot/js/components/chat/username.js deleted file mode 100644 index f505a017f..000000000 --- a/webroot/js/components/chat/username.js +++ /dev/null @@ -1,163 +0,0 @@ -import { h, Component, createRef } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -const html = htm.bind(h); - -import { setLocalStorage } from '../../utils/helpers.js'; -import { - KEY_USERNAME, - KEY_CUSTOM_USERNAME_SET, -} from '../../utils/constants.js'; - -import { CheckIcon, CloseIcon, EditIcon } from '../icons/index.js'; - -export default class UsernameForm extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - displayForm: false, - isFocused: false, - }; - - this.textInput = createRef(); - - this.handleKeydown = this.handleKeydown.bind(this); - this.handleDisplayForm = this.handleDisplayForm.bind(this); - this.handleHideForm = this.handleHideForm.bind(this); - this.handleUpdateUsername = this.handleUpdateUsername.bind(this); - this.handleFocus = this.handleFocus.bind(this); - this.handleBlur = this.handleBlur.bind(this); - } - - handleDisplayForm() { - const { displayForm: curDisplay } = this.state; - this.setState({ - displayForm: !curDisplay, - }); - } - - handleHideForm() { - this.setState({ - displayForm: false, - }); - } - - handleKeydown(event) { - if (event.keyCode === 13) { - // enter - this.handleUpdateUsername(); - } else if (event.keyCode === 27) { - // esc - this.handleHideForm(); - } - } - - handleUpdateUsername() { - const { username: curName, onUsernameChange } = this.props; - let newName = this.textInput.current.value; - newName = newName.trim(); - if (newName !== '' && newName !== curName) { - setLocalStorage(KEY_USERNAME, newName); - // So we know that the user has set a custom name - setLocalStorage(KEY_CUSTOM_USERNAME_SET, true); - if (onUsernameChange) { - onUsernameChange(newName); - } - this.handleHideForm(); - } else { - this.handleHideForm(); - } - } - - handleFocus() { - const { onFocus } = this.props; - if (onFocus) { - onFocus(); - } - } - - handleBlur() { - const { onBlur } = this.props; - if (onBlur) { - onBlur(); - } - } - - componentDidUpdate({}, { displayForm }) { - if (this.state.displayForm && !displayForm) { - document.getElementById('username-change-input').select(); - } - } - - render(props, state) { - const { username, isModerator } = props; - const { displayForm } = state; - - const styles = { - info: { - display: displayForm ? 'none' : 'flex', - }, - form: { - display: displayForm ? 'flex' : 'none', - }, - }; - - const moderatorFlag = html` - - `; - - return html` -
    - - -
    - -
    - - - -
    -
    -
    - `; - } -} diff --git a/webroot/js/components/external-action-modal.js b/webroot/js/components/external-action-modal.js deleted file mode 100644 index 0ce35b026..000000000 --- a/webroot/js/components/external-action-modal.js +++ /dev/null @@ -1,128 +0,0 @@ -import { h, Component } from '/js/web_modules/preact.js'; -import htm from '/js/web_modules/htm.js'; -import MicroModal from '/js/web_modules/micromodal/dist/micromodal.min.js'; - -const html = htm.bind(h); - -export default class ExternalActionModal extends Component { - constructor(props) { - super(props); - this.state = { - iframeLoaded: false, - }; - - this.setIframeLoaded = this.setIframeLoaded.bind(this); - } - componentDidMount() { - // initialize and display Micromodal on mount - try { - MicroModal.init({ - awaitCloseAnimation: false, - awaitOpenAnimation: true, // if using css animations to open the modal. This allows it to wait for the animation to finish before focusing on an element inside the modal. - }); - MicroModal.show('external-actions-modal', { - onClose: this.props.onClose, - }); - } catch (e) { - console.error('modal error: ', e); - } - } - - setIframeLoaded() { - this.setState({ - iframeLoaded: true, - }); - } - - render() { - const { action, useIframe = true, customContent = null } = this.props; - const { url, title, description } = action; - const { iframeLoaded } = this.state; - const iframeStyle = iframeLoaded - ? null - : { backgroundImage: 'url(/img/loading.gif)' }; - - return html` -