diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f38395637ca3ee506946ea31de3bd130810cffb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel + +# redis dump +dump.rdb + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +.vscode/* + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..319e41e69dc9814508dd664b4162dc4a599cac1c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +strict-peer-dependencies=false diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6946f10a00f5c5899748a0c90710f7bec3a6cab9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Spencer Woo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/config/api.config.js b/config/api.config.js new file mode 100644 index 0000000000000000000000000000000000000000..c96e53e4f1809920f21db7acda95f1284090efa9 --- /dev/null +++ b/config/api.config.js @@ -0,0 +1,34 @@ +/** + * This file contains the configuration for the API endpoints and tokens we use. + * + * - If you are a OneDrive International user, you would not have to change anything here. + * - If you are not the admin of your OneDrive for Business account, you may need to define your own clientId/clientSecret, + * check documentation for more details. + * - If you are using a E5 Subscription OneDrive for Business account, the direct links of your files are not the same here. + * In which case you would need to change directLinkRegex. + */ +module.exports = { + // The clientId and clientSecret are used to authenticate the user with Microsoft Graph API using OAuth. You would + // not need to change anything here if you can authenticate with your personal Microsoft account with OneDrive International. + clientId: '240fc1d2-51ec-41a1-b482-8019c272f7de', + obfuscatedClientSecret: 'U2FsdGVkX19/yeKIAt4R9Q4mgxVxFNHSbN4kjbM9rlm4bakBdhNA2Iq4NADdNQSsyOqZzqV1eQefjYfUxP/OSw==', + + // The redirectUri is the URL that the user will be redirected to after they have authenticated with Microsoft Graph API. + // Likewise, you would not need to change redirectUri if you are using your personal Microsoft account with OneDrive International. + redirectUri: 'http://localhost', + + // These are the URLs of the OneDrive API endpoints. You would not need to change anything here if you are using OneDrive International + // or E5 Subscription OneDrive for Business. You may need to change these if you are using OneDrive 世纪互联. + authApi: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + driveApi: 'https://graph.microsoft.com/v1.0/me/drive', + + // The scope we require are listed here, in most cases you would not need to change this as well. + scope: 'user.read files.read.all offline_access', + + // Cache-Control header, check Vercel documentation for more details. The default settings imply: + // - max-age=0: no cache for your browser + // - s-maxage=0: cache is fresh for 60 seconds on the edge, after which it becomes stale + // - stale-while-revalidate: allow serving stale content while revalidating on the edge + // https://vercel.com/docs/concepts/edge-network/caching + cacheControlHeader: 'max-age=0, s-maxage=60, stale-while-revalidate', +} diff --git a/config/site.config.js b/config/site.config.js new file mode 100644 index 0000000000000000000000000000000000000000..5a4ab05bb9379fcfb110aac34e09b99432d563d4 --- /dev/null +++ b/config/site.config.js @@ -0,0 +1,68 @@ +/** + * This file contains the configuration used for customising the website, such as the folder to share, + * the title, used Google fonts, site icons, contact info, etc. + */ +module.exports = { + // This is what we use to identify who you are when you are initialising the website for the first time. + // Make sure this is exactly the same as the email address you use to sign into your Microsoft account. + // You can also put this in your Vercel's environment variable 'NEXT_PUBLIC_USER_PRINCIPLE_NAME' if you worry about + // your email being exposed in public. + userPrincipalName: process.env.NEXT_PUBLIC_USER_PRINCIPLE_NAME || '', + + // [OPTIONAL] This is the website icon to the left of the title inside the navigation bar. It should be placed under the + // /public directory of your GitHub project (not your OneDrive folder!), and referenced here by its relative path to /public. + icon: '/icons/ap.png', + + // Prefix for KV Storage + kvPrefix: process.env.KV_PREFIX || '', + + // The name of your website. Present alongside your icon. + title: "AyamPenyet's Drive", + + // The folder that you are to share publicly with onedrive-vercel-index. Use '/' if you want to share your root folder. + baseDirectory: '/Public', + + // [OPTIONAL] This represents the maximum number of items that one directory lists, pagination supported. + // Do note that this is limited up to 200 items by the upstream OneDrive API. + maxItems: 100, + + // [OPTIONAL] We use Google Fonts natively for font customisations. + // You can check and generate the required links and names at https://fonts.google.com. + // googleFontSans - the sans serif font used in onedrive-vercel-index. + googleFontSans: 'Inter', + // googleFontMono - the monospace font used in onedrive-vercel-index. + googleFontMono: 'Fira Mono', + // googleFontLinks - an array of links for referencing the google font assets. + googleFontLinks: ['https://fonts.googleapis.com/css2?family=Fira+Mono&family=Inter:wght@400;500;700&display=swap'], + + // [OPTIONAL] The footer component of your website. You can write HTML here, but you need to escape double + // quotes - changing " to \". You can write anything here, and if you like badges, generate some with https://shields.io + footer: + 'Powered by onedrive-vercel-index. 🥱 Ayam Penyet.', + + // [OPTIONAL] This is where you specify the folders that are password protected. It is an array of paths pointing to all + // the directories in which you have .password set. Check the documentation for details. + protectedRoutes: ['/🔞 Private',], + + // [OPTIONAL] Use "" here if you want to remove this email address from the nav bar. + email: '', + + // [OPTIONAL] This is an array of names and links for setting your social information and links. + // In the latest update, all brand icons inside font awesome is supported and the icon to render is based on the name + // you provide. See the documentation for details. + links: [ + { + name: 'Discord', + link: 'http://discordapp.com/users/253478920626634752', + }, + { + name: 'GitHub', + link: 'https://github.com/ImPeekaboo', + }, + ], + + // This is a day.js-style datetime format string to format datetimes in the app. Ref to + // https://day.js.org/docs/en/display/format for detailed specification. The default value is ISO 8601 full datetime + // without timezone and replacing T with space. + datetimeFormat: 'YYYY-MM-DD HH:mm:ss', +} diff --git a/i18next-parser.config.js b/i18next-parser.config.js new file mode 100644 index 0000000000000000000000000000000000000000..439b39f948047d5c00caf7a9b7e5bb6af5c9b01f --- /dev/null +++ b/i18next-parser.config.js @@ -0,0 +1,18 @@ +const path = require('path') + +const { i18n, localePath } = require('./next-i18next.config') + +module.exports = { + createOldCatalogs: false, + defaultNamespace: 'common', + defaultValue: (lng, _ns, key) => (lng === i18n.defaultLocale ? key : ''), + keySeparator: false, + namespaceSeparator: false, + pluralSeparator: '——', + contextSeparator: '——', + lineEnding: 'lf', + locales: i18n.locales, + output: path.join(localePath, '$LOCALE/$NAMESPACE.json'), + input: ['**/*.{ts,tsx}', '!**/node_modules/**'], + sort: true +} diff --git a/i18next.d.ts b/i18next.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f47dfe81116736645a1a081ba4df16aeb4aac33 --- /dev/null +++ b/i18next.d.ts @@ -0,0 +1,9 @@ +import 'i18next' + +declare module 'i18next' { + interface CustomTypeOptions { + // This is set to prevent i18next's t function to return null + // https://github.com/i18next/next-i18next/issues/2038 + returnNull: false + } +} diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f11a03dc6cc37f2b5105c08f2e7b24c603ab2f4 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..9eda059493234f064015e1517f0451d7ab41aef2 --- /dev/null +++ b/next-i18next.config.js @@ -0,0 +1,14 @@ +const path = require('path') + +module.exports = { + i18n: { + defaultLocale: 'en', + locales: ['de-DE', 'en', 'es', 'zh-CN', 'hi', 'id', 'tr-TR', 'zh-TW'] + }, + localePath: path.resolve('public/locales'), + reloadOnPrerender: process.env.NODE_ENV === 'development', + keySeparator: false, + namespaceSeparator: false, + pluralSeparator: '——', + contextSeparator: '——' +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..fa13553c28af7f4eabecde434d6b0487dde1fe86 --- /dev/null +++ b/next.config.js @@ -0,0 +1,8 @@ +const { i18n } = require('./next-i18next.config') + +module.exports = { + i18n, + reactStrictMode: true, + // Required by Next i18n with API routes, otherwise API routes 404 when fetching without trailing slash + trailingSlash: true +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..605d54fa77a4cba74fd69340e0cd3c47e31a5340 --- /dev/null +++ b/package.json @@ -0,0 +1,95 @@ +{ + "name": "onedrive-vercel-index", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "format": "prettier 'src/**/*.{js,ts,jsx,tsx}' --write", + "extract": "i18next" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/free-brands-svg-icons": "^6.2.1", + "@fortawesome/free-regular-svg-icons": "^6.2.1", + "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "@headlessui/react": "^1.7.8", + "@tailwindcss/line-clamp": "^0.4.2", + "@vercel/analytics": "^1.0.1", + "awesome-debounce-promise": "^2.1.0", + "axios": "^1.2.6", + "cors": "^2.8.5", + "crypto-js": "^4.1.1", + "csstype": "^3.1.1", + "dayjs": "^1.11.7", + "emoji-regex": "^10.2.1", + "i18next": "^22.4.9", + "ioredis": "^5.3.0", + "jszip": "^3.10.1", + "mpegts.js": "^1.7.2", + "next": "^13.1.6", + "next-i18next": "^13.0.3", + "nextjs-progressbar": "^0.0.16", + "plyr-react": "^5.1.2", + "preview-office-docs": "^1.0.2", + "react": "^18.2.0", + "react-async-hook": "^4.0.0", + "react-audio-player": "^0.17.0", + "react-cookie": "^4.1.1", + "react-copy-to-clipboard": "^5.0.3", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.0", + "react-hotkeys-hook": "^4.3.3", + "react-i18next": "^12.1.4", + "react-markdown": "^8.0.5", + "react-reader": "^1.0.2", + "react-syntax-highlighter": "^15.5.0", + "react-use-system-theme": "^1.1.1", + "rehype-katex": "^6.0.2", + "rehype-raw": "^6.0.0", + "remark-gfm": "^3.0.1", + "remark-math": "^5.1.1", + "swr": "^2.0.3", + "use-clipboard-copy": "^0.2.0", + "use-constant": "^1.1.1" + }, + "devDependencies": { + "@types/cors": "^2.8.13", + "@types/crypto-js": "^4.0.2", + "@types/node": "18.16.18", + "@types/react": "18.2.14", + "@types/react-copy-to-clipboard": "^5.0.4", + "@types/react-dom": "^18.0.10", + "@types/react-pdf": "^6.2.0", + "@types/react-syntax-highlighter": "^15.5.6", + "autoprefixer": "^10.4.13", + "eslint": "8.43.0", + "eslint-config-next": "13.4.7", + "eslint-config-prettier": "^8.6.0", + "i18next-parser": "^7.6.0", + "postcss": "^8.4.21", + "prettier": "^2.8.3", + "prettier-plugin-tailwindcss": "^0.2.2", + "tailwindcss": "^3.2.4", + "typescript": "5.1.3" + }, + "prettier": { + "printWidth": 120, + "arrowParens": "avoid", + "singleQuote": true, + "semi": false, + "plugins": [ + "prettier-plugin-tailwindcss" + ] + }, + "eslintConfig": { + "extends": [ + "next", + "next/core-web-vitals", + "prettier" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..52d33fba9767a04498173ee5e8878177280cae7c --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5597 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@fortawesome/fontawesome-svg-core': + specifier: ^6.2.1 + version: 6.2.1 + '@fortawesome/free-brands-svg-icons': + specifier: ^6.2.1 + version: 6.2.1 + '@fortawesome/free-regular-svg-icons': + specifier: ^6.2.1 + version: 6.2.1 + '@fortawesome/free-solid-svg-icons': + specifier: ^6.2.1 + version: 6.2.1 + '@fortawesome/react-fontawesome': + specifier: ^0.2.0 + version: 0.2.0(@fortawesome/fontawesome-svg-core@6.2.1)(react@18.2.0) + '@headlessui/react': + specifier: ^1.7.8 + version: 1.7.8(react-dom@18.2.0)(react@18.2.0) + '@tailwindcss/line-clamp': + specifier: ^0.4.2 + version: 0.4.2(tailwindcss@3.2.4) + '@vercel/analytics': + specifier: ^1.0.1 + version: 1.0.1 + awesome-debounce-promise: + specifier: ^2.1.0 + version: 2.1.0 + axios: + specifier: ^1.2.6 + version: 1.2.6 + cors: + specifier: ^2.8.5 + version: 2.8.5 + crypto-js: + specifier: ^4.1.1 + version: 4.1.1 + csstype: + specifier: ^3.1.1 + version: 3.1.1 + dayjs: + specifier: ^1.11.7 + version: 1.11.7 + emoji-regex: + specifier: ^10.2.1 + version: 10.2.1 + i18next: + specifier: ^22.4.9 + version: 22.4.9 + ioredis: + specifier: ^5.3.0 + version: 5.3.0 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + mpegts.js: + specifier: ^1.7.2 + version: 1.7.2 + next: + specifier: ^13.1.6 + version: 13.1.6(react-dom@18.2.0)(react@18.2.0) + next-i18next: + specifier: ^13.0.3 + version: 13.0.3(i18next@22.4.9)(next@13.1.6)(react-i18next@12.1.4)(react@18.2.0) + nextjs-progressbar: + specifier: ^0.0.16 + version: 0.0.16(next@13.1.6)(react@18.2.0) + plyr-react: + specifier: ^5.1.2 + version: 5.1.2(plyr@3.7.3)(react@18.2.0) + preview-office-docs: + specifier: ^1.0.2 + version: 1.0.2(react@18.2.0) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-async-hook: + specifier: ^4.0.0 + version: 4.0.0(react@18.2.0) + react-audio-player: + specifier: ^0.17.0 + version: 0.17.0(react-dom@18.2.0)(react@18.2.0) + react-cookie: + specifier: ^4.1.1 + version: 4.1.1(react@18.2.0) + react-copy-to-clipboard: + specifier: ^5.0.3 + version: 5.1.0(react@18.2.0) + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-hot-toast: + specifier: ^2.4.0 + version: 2.4.0(csstype@3.1.1)(react-dom@18.2.0)(react@18.2.0) + react-hotkeys-hook: + specifier: ^4.3.3 + version: 4.3.3(react-dom@18.2.0)(react@18.2.0) + react-i18next: + specifier: ^12.1.4 + version: 12.1.4(i18next@22.4.9)(react-dom@18.2.0)(react@18.2.0) + react-markdown: + specifier: ^8.0.5 + version: 8.0.5(@types/react@18.2.14)(react@18.2.0) + react-reader: + specifier: ^1.0.2 + version: 1.0.2(react@18.2.0) + react-syntax-highlighter: + specifier: ^15.5.0 + version: 15.5.0(react@18.2.0) + react-use-system-theme: + specifier: ^1.1.1 + version: 1.1.1(react@18.2.0) + rehype-katex: + specifier: ^6.0.2 + version: 6.0.2 + rehype-raw: + specifier: ^6.0.0 + version: 6.1.1 + remark-gfm: + specifier: ^3.0.1 + version: 3.0.1 + remark-math: + specifier: ^5.1.1 + version: 5.1.1 + swr: + specifier: ^2.0.3 + version: 2.0.3(react@18.2.0) + use-clipboard-copy: + specifier: ^0.2.0 + version: 0.2.0(react@18.2.0) + use-constant: + specifier: ^1.1.1 + version: 1.1.1(react@18.2.0) + +devDependencies: + '@types/cors': + specifier: ^2.8.13 + version: 2.8.13 + '@types/crypto-js': + specifier: ^4.0.2 + version: 4.1.1 + '@types/node': + specifier: 18.16.18 + version: 18.16.18 + '@types/react': + specifier: 18.2.14 + version: 18.2.14 + '@types/react-copy-to-clipboard': + specifier: ^5.0.4 + version: 5.0.4 + '@types/react-dom': + specifier: ^18.0.10 + version: 18.0.10 + '@types/react-pdf': + specifier: ^6.2.0 + version: 6.2.0 + '@types/react-syntax-highlighter': + specifier: ^15.5.6 + version: 15.5.6 + autoprefixer: + specifier: ^10.4.13 + version: 10.4.13(postcss@8.4.21) + eslint: + specifier: 8.43.0 + version: 8.43.0 + eslint-config-next: + specifier: 13.4.7 + version: 13.4.7(eslint@8.43.0)(typescript@5.1.3) + eslint-config-prettier: + specifier: ^8.6.0 + version: 8.6.0(eslint@8.43.0) + i18next-parser: + specifier: ^7.6.0 + version: 7.6.0 + postcss: + specifier: ^8.4.21 + version: 8.4.21 + prettier: + specifier: ^2.8.3 + version: 2.8.3 + prettier-plugin-tailwindcss: + specifier: ^0.2.2 + version: 0.2.2(prettier@2.8.3) + tailwindcss: + specifier: ^3.2.4 + version: 3.2.4(postcss@8.4.21) + typescript: + specifier: 5.1.3 + version: 5.1.3 + +packages: + + /@babel/runtime@7.19.0: + resolution: {integrity: sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + dev: false + + /@babel/runtime@7.20.13: + resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + + /@esbuild/android-arm64@0.17.4: + resolution: {integrity: sha512-91VwDrl4EpxBCiG6h2LZZEkuNvVZYJkv2T9gyLG/mhGG1qrM7i5SwUcg/hlSPnL/4hDT0TFcF35/XMGSn0bemg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.17.4: + resolution: {integrity: sha512-R9GCe2xl2XDSc2XbQB63mFiFXHIVkOP+ltIxICKXqUPrFX97z6Z7vONCLQM1pSOLGqfLrGi3B7nbhxmFY/fomg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.17.4: + resolution: {integrity: sha512-mGSqhEPL7029XL7QHNPxPs15JVa02hvZvysUcyMP9UXdGFwncl2WU0bqx+Ysgzd+WAbv8rfNa73QveOxAnAM2w==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.17.4: + resolution: {integrity: sha512-tTyJRM9dHvlMPt1KrBFVB5OW1kXOsRNvAPtbzoKazd5RhD5/wKlXk1qR2MpaZRYwf4WDMadt0Pv0GwxB41CVow==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.17.4: + resolution: {integrity: sha512-phQuC2Imrb3TjOJwLN8EO50nb2FHe8Ew0OwgZDH1SV6asIPGudnwTQtighDF2EAYlXChLoMJwqjAp4vAaACq6w==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.17.4: + resolution: {integrity: sha512-oH6JUZkocgmjzzYaP5juERLpJQSwazdjZrTPgLRmAU2bzJ688x0vfMB/WTv4r58RiecdHvXOPC46VtsMy/mepg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.17.4: + resolution: {integrity: sha512-U4iWGn/9TrAfpAdfd56eO0pRxIgb0a8Wj9jClrhT8hvZnOnS4dfMPW7o4fn15D/KqoiVYHRm43jjBaTt3g/2KA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.17.4: + resolution: {integrity: sha512-UkGfQvYlwOaeYJzZG4cLV0hCASzQZnKNktRXUo3/BMZvdau40AOz9GzmGA063n1piq6VrFFh43apRDQx8hMP2w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.17.4: + resolution: {integrity: sha512-S2s9xWTGMTa/fG5EyMGDeL0wrWVgOSQcNddJWgu6rG1NCSXJHs76ZP9AsxjB3f2nZow9fWOyApklIgiTGZKhiw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.17.4: + resolution: {integrity: sha512-3lqFi4VFo/Vwvn77FZXeLd0ctolIJH/uXkH3yNgEk89Eh6D3XXAC9/iTPEzeEpsNE5IqGIsFa5Z0iPeOh25IyA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.17.4: + resolution: {integrity: sha512-HqpWZkVslDHIwdQ9D+gk7NuAulgQvRxF9no54ut/M55KEb3mi7sQS3GwpPJzSyzzP0UkjQVN7/tbk88/CaX4EQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.17.4: + resolution: {integrity: sha512-d/nMCKKh/SVDbqR9ju+b78vOr0tNXtfBjcp5vfHONCCOAL9ad8gN9dC/u+UnH939pz7wO+0u/x9y1MaZcb/lKA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.17.4: + resolution: {integrity: sha512-lOD9p2dmjZcNiTU+sGe9Nn6G3aYw3k0HBJies1PU0j5IGfp6tdKOQ6mzfACRFCqXjnBuTqK7eTYpwx09O5LLfg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.17.4: + resolution: {integrity: sha512-mTGnwWwVshAjGsd8rP+K6583cPDgxOunsqqldEYij7T5/ysluMHKqUIT4TJHfrDFadUwrghAL6QjER4FeqQXoA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.17.4: + resolution: {integrity: sha512-AQYuUGp50XM29/N/dehADxvc2bUqDcoqrVuijop1Wv72SyxT6dDB9wjUxuPZm2HwIM876UoNNBMVd+iX/UTKVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.17.4: + resolution: {integrity: sha512-+AsFBwKgQuhV2shfGgA9YloxLDVjXgUEWZum7glR5lLmV94IThu/u2JZGxTgjYby6kyXEx8lKOqP5rTEVBR0Rw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.17.4: + resolution: {integrity: sha512-zD1TKYX9553OiLS/qkXPMlWoELYkH/VkzRYNKEU+GwFiqkq0SuxsKnsCg5UCdxN3cqd+1KZ8SS3R+WG/Hxy2jQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.17.4: + resolution: {integrity: sha512-PY1NjEsLRhPEFFg1AV0/4Or/gR+q2dOb9s5rXcPuCjyHRzbt8vnHJl3vYj+641TgWZzTFmSUnZbzs1zwTzjeqw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.17.4: + resolution: {integrity: sha512-B3Z7s8QZQW9tKGleMRXvVmwwLPAUoDCHs4WZ2ElVMWiortLJFowU1NjAhXOKjDgC7o9ByeVcwyOlJ+F2r6ZgmQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.17.4: + resolution: {integrity: sha512-0HCu8R3mY/H5V7N6kdlsJkvrT591bO/oRZy8ztF1dhgNU5xD5tAh5bKByT1UjTGjp/VVBsl1PDQ3L18SfvtnBQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.17.4: + resolution: {integrity: sha512-VUjhVDQycse1gLbe06pC/uaA0M+piQXJpdpNdhg8sPmeIZZqu5xPoGWVCmcsOO2gaM2cywuTYTHkXRozo3/Nkg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.17.4: + resolution: {integrity: sha512-0kLAjs+xN5OjhTt/aUA6t48SfENSCKgGPfExADYTOo/UCn0ivxos9/anUVeSfg+L+2O9xkFxvJXIJfG+Q4sYSg==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint-community/eslint-utils@4.2.0(eslint@8.43.0): + resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.43.0 + eslint-visitor-keys: 3.4.1 + dev: true + + /@eslint-community/regexpp@4.4.0: + resolution: {integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.0.3: + resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.5.2 + globals: 13.20.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.43.0: + resolution: {integrity: sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@fortawesome/fontawesome-common-types@6.2.1: + resolution: {integrity: sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ==} + engines: {node: '>=6'} + requiresBuild: true + dev: false + + /@fortawesome/fontawesome-svg-core@6.2.1: + resolution: {integrity: sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@fortawesome/fontawesome-common-types': 6.2.1 + dev: false + + /@fortawesome/free-brands-svg-icons@6.2.1: + resolution: {integrity: sha512-L8l4MfdHPmZlJ72PvzdfwOwbwcCAL0vx48tJRnI6u1PJXh+j2f3yDoKyQgO3qjEsgD5Fr2tQV/cPP8F/k6aUig==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@fortawesome/fontawesome-common-types': 6.2.1 + dev: false + + /@fortawesome/free-regular-svg-icons@6.2.1: + resolution: {integrity: sha512-wiqcNDNom75x+pe88FclpKz7aOSqS2lOivZeicMV5KRwOAeypxEYWAK/0v+7r+LrEY30+qzh8r2XDaEHvoLsMA==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@fortawesome/fontawesome-common-types': 6.2.1 + dev: false + + /@fortawesome/free-solid-svg-icons@6.2.1: + resolution: {integrity: sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@fortawesome/fontawesome-common-types': 6.2.1 + dev: false + + /@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@6.2.1)(react@18.2.0): + resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==} + peerDependencies: + '@fortawesome/fontawesome-svg-core': ~1 || ~6 + react: '>=16.3' + dependencies: + '@fortawesome/fontawesome-svg-core': 6.2.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@headlessui/react@1.7.8(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-zcwb0kd7L05hxmoAMIioEaOn235Dg0fUO+iGbLPgLVSjzl/l39V6DTpC2Df49PE5aG5/f5q0PZ9ZHZ78ENNV+A==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + dependencies: + client-only: 0.0.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@humanwhocodes/config-array@0.11.10: + resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false + + /@next/env@13.1.6: + resolution: {integrity: sha512-s+W9Fdqh5MFk6ECrbnVmmAOwxKQuhGMT7xXHrkYIBMBcTiOqNWhv5KbJIboKR5STXxNXl32hllnvKaffzFaWQg==} + dev: false + + /@next/eslint-plugin-next@13.4.7: + resolution: {integrity: sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg==} + dependencies: + glob: 7.1.7 + dev: true + + /@next/swc-android-arm-eabi@13.1.6: + resolution: {integrity: sha512-F3/6Z8LH/pGlPzR1AcjPFxx35mPqjE5xZcf+IL+KgbW9tMkp7CYi1y7qKrEWU7W4AumxX/8OINnDQWLiwLasLQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-android-arm64@13.1.6: + resolution: {integrity: sha512-cMwQjnB8vrYkWyK/H0Rf2c2pKIH4RGjpKUDvbjVAit6SbwPDpmaijLio0LWFV3/tOnY6kvzbL62lndVA0mkYpw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-arm64@13.1.6: + resolution: {integrity: sha512-KKRQH4DDE4kONXCvFMNBZGDb499Hs+xcFAwvj+rfSUssIDrZOlyfJNy55rH5t2Qxed1e4K80KEJgsxKQN1/fyw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64@13.1.6: + resolution: {integrity: sha512-/uOky5PaZDoaU99ohjtNcDTJ6ks/gZ5ykTQDvNZDjIoCxFe3+t06bxsTPY6tAO6uEAw5f6vVFX5H5KLwhrkZCA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-freebsd-x64@13.1.6: + resolution: {integrity: sha512-qaEALZeV7to6weSXk3Br80wtFQ7cFTpos/q+m9XVRFggu+8Ib895XhMWdJBzew6aaOcMvYR6KQ6JmHA2/eMzWw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm-gnueabihf@13.1.6: + resolution: {integrity: sha512-OybkbC58A1wJ+JrJSOjGDvZzrVEQA4sprJejGqMwiZyLqhr9Eo8FXF0y6HL+m1CPCpPhXEHz/2xKoYsl16kNqw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu@13.1.6: + resolution: {integrity: sha512-yCH+yDr7/4FDuWv6+GiYrPI9kcTAO3y48UmaIbrKy8ZJpi7RehJe3vIBRUmLrLaNDH3rY1rwoHi471NvR5J5NQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl@13.1.6: + resolution: {integrity: sha512-ECagB8LGX25P9Mrmlc7Q/TQBb9rGScxHbv/kLqqIWs2fIXy6Y/EiBBiM72NTwuXUFCNrWR4sjUPSooVBJJ3ESQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu@13.1.6: + resolution: {integrity: sha512-GT5w2mruk90V/I5g6ScuueE7fqj/d8Bui2qxdw6lFxmuTgMeol5rnzAv4uAoVQgClOUO/MULilzlODg9Ib3Y4Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl@13.1.6: + resolution: {integrity: sha512-keFD6KvwOPzmat4TCnlnuxJCQepPN+8j3Nw876FtULxo8005Y9Ghcl7ACcR8GoiKoddAq8gxNBrpjoxjQRHeAQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc@13.1.6: + resolution: {integrity: sha512-OwertslIiGQluFvHyRDzBCIB07qJjqabAmINlXUYt7/sY7Q7QPE8xVi5beBxX/rxTGPIbtyIe3faBE6Z2KywhQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-ia32-msvc@13.1.6: + resolution: {integrity: sha512-g8zowiuP8FxUR9zslPmlju7qYbs2XBtTLVSxVikPtUDQedhcls39uKYLvOOd1JZg0ehyhopobRoH1q+MHlIN/w==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc@13.1.6: + resolution: {integrity: sha512-Ls2OL9hi3YlJKGNdKv8k3X/lLgc3VmLG3a/DeTkAd+lAituJp8ZHmRmm9f9SL84fT3CotlzcgbdaCDfFwFA6bA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + + /@pkgr/utils@2.3.1: + resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + is-glob: 4.0.3 + open: 8.4.0 + picocolors: 1.0.0 + tiny-glob: 0.2.9 + tslib: 2.5.0 + dev: true + + /@rushstack/eslint-patch@1.2.0: + resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} + dev: true + + /@swc/helpers@0.4.14: + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + dependencies: + tslib: 2.5.0 + dev: false + + /@tailwindcss/line-clamp@0.4.2(tailwindcss@3.2.4): + resolution: {integrity: sha512-HFzAQuqYCjyy/SX9sLGB1lroPzmcnWv1FHkIpmypte10hptf4oPUfucryMKovZh2u0uiS9U5Ty3GghWfEJGwVw==} + peerDependencies: + tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' + dependencies: + tailwindcss: 3.2.4(postcss@8.4.21) + dev: false + + /@types/cookie@0.3.3: + resolution: {integrity: sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==} + dev: false + + /@types/cors@2.8.13: + resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} + dependencies: + '@types/node': 18.16.18 + dev: true + + /@types/crypto-js@4.1.1: + resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==} + dev: true + + /@types/debounce-promise@3.1.4: + resolution: {integrity: sha512-9SEVY3nsz+uMN2DwDocftB5TAgZe7D0cOzxxRhpotWs6T4QFqRaTXpXbOSzbk31/7iYcfCkJJPwWGzTxyuGhCg==} + dev: false + + /@types/debug@4.1.7: + resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} + dependencies: + '@types/ms': 0.7.31 + dev: false + + /@types/hast@2.3.4: + resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /@types/hoist-non-react-statics@3.3.1: + resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} + dependencies: + '@types/react': 18.2.14 + hoist-non-react-statics: 3.3.2 + dev: false + + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true + + /@types/katex@0.11.1: + resolution: {integrity: sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==} + dev: false + + /@types/localforage@0.0.34: + resolution: {integrity: sha1-XjHDLdh5HsS5/z70fJy1Wy0NlDg=} + deprecated: This is a stub types definition for localforage (https://github.com/localForage/localForage). localforage provides its own type definitions, so you don't need @types/localforage installed! + dependencies: + localforage: 1.10.0 + dev: false + + /@types/mdast@3.0.10: + resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /@types/minimatch@3.0.5: + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + dev: true + + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: false + + /@types/node@18.16.18: + resolution: {integrity: sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw==} + dev: true + + /@types/nprogress@0.2.0: + resolution: {integrity: sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==} + dev: false + + /@types/parse5@6.0.3: + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + dev: false + + /@types/prop-types@15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + /@types/react-copy-to-clipboard@5.0.4: + resolution: {integrity: sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==} + dependencies: + '@types/react': 18.2.14 + dev: true + + /@types/react-dom@18.0.10: + resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==} + dependencies: + '@types/react': 18.2.14 + dev: true + + /@types/react-pdf@6.2.0: + resolution: {integrity: sha512-OSCYmrfaJvpXkM5V4seUMAhUDOAOqbGQf9kwv14INyTf7AjDs2ukfkkQrLWRQ8OjWrDklbXYWh5l7pT7l0N76g==} + dependencies: + '@types/react': 18.2.14 + pdfjs-dist: 2.16.105 + transitivePeerDependencies: + - worker-loader + dev: true + + /@types/react-syntax-highlighter@15.5.6: + resolution: {integrity: sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==} + dependencies: + '@types/react': 18.2.14 + dev: true + + /@types/react@18.2.14: + resolution: {integrity: sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + + /@types/scheduler@0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + + /@types/symlink-or-copy@1.2.0: + resolution: {integrity: sha512-Lja2xYuuf2B3knEsga8ShbOdsfNOtzT73GyJmZyY7eGl2+ajOqrs8yM5ze0fsSoYwvA6bw7/Qr7OZ7PEEmYwWg==} + dev: true + + /@types/unist@2.0.6: + resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} + dev: false + + /@typescript-eslint/parser@5.49.0(eslint@8.43.0)(typescript@5.1.3): + resolution: {integrity: sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.49.0 + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/typescript-estree': 5.49.0(typescript@5.1.3) + debug: 4.3.4 + eslint: 8.43.0 + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@5.49.0: + resolution: {integrity: sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/visitor-keys': 5.49.0 + dev: true + + /@typescript-eslint/types@5.49.0: + resolution: {integrity: sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.49.0(typescript@5.1.3): + resolution: {integrity: sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/visitor-keys': 5.49.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0(typescript@5.1.3) + typescript: 5.1.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/visitor-keys@5.49.0: + resolution: {integrity: sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.49.0 + eslint-visitor-keys: 3.4.1 + dev: true + + /@vercel/analytics@1.0.1: + resolution: {integrity: sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==} + dev: false + + /@xmldom/xmldom@0.7.9: + resolution: {integrity: sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA==} + engines: {node: '>=10.0.0'} + dev: false + + /acorn-jsx@5.3.2(acorn@8.8.2): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.2 + dev: true + + /acorn-node@1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + + /acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + + /acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + /acorn@8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /append-buffer@1.0.2: + resolution: {integrity: sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==} + engines: {node: '>=0.10.0'} + dependencies: + buffer-equal: 1.0.1 + dev: true + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + dependencies: + deep-equal: 2.2.0 + dev: true + + /array-includes@3.1.6: + resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + get-intrinsic: 1.2.0 + is-string: 1.0.7 + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + + /array.prototype.flatmap@1.3.1: + resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + + /array.prototype.tosorted@1.1.1: + resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.0 + dev: true + + /ast-types-flow@0.0.7: + resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /autoprefixer@10.4.13(postcss@8.4.21): + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.4 + caniuse-lite: 1.0.30001448 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /awesome-debounce-promise@2.1.0: + resolution: {integrity: sha512-0Dv4j2wKk5BrNZh4jgV2HUdznaeVgEK/WTvcHhZWUElhmQ1RR+iURRoLEwICFyR0S/5VtxfcvY6gR+qSe95jNg==} + engines: {node: '>=8', npm: '>=5'} + dependencies: + '@types/debounce-promise': 3.1.4 + awesome-imperative-promise: 1.0.1 + awesome-only-resolves-last-promise: 1.0.3 + debounce-promise: 3.1.2 + dev: false + + /awesome-imperative-promise@1.0.1: + resolution: {integrity: sha512-EmPr3FqbQGqlNh+WxMNcF9pO9uDQJnOC4/3rLBQNH9m4E9qI+8lbfHCmHpVAsmGqPJPKhCjJLHUQzQW/RBHRdQ==} + engines: {node: '>=8', npm: '>=5'} + dev: false + + /awesome-only-resolves-last-promise@1.0.3: + resolution: {integrity: sha512-7q4WPsYiD8Omvi/yHL314DkvsD/lM//Z2/KcU1vWk0xJotiV0GMJTgHTpWl3n90HJqpXKg7qX+VVNs5YbQyPRQ==} + engines: {node: '>=8', npm: '>=5'} + dependencies: + awesome-imperative-promise: 1.0.1 + dev: false + + /axe-core@4.6.3: + resolution: {integrity: sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==} + engines: {node: '>=4'} + dev: true + + /axios@1.2.6: + resolution: {integrity: sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /axobject-query@3.1.1: + resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} + dependencies: + deep-equal: 2.2.0 + dev: true + + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /broccoli-node-api@1.7.0: + resolution: {integrity: sha512-QIqLSVJWJUVOhclmkmypJJH9u9s/aWH4+FH6Q6Ju5l+Io4dtwqdPUNmDfw40o6sxhbZHhqGujDJuHTML1wG8Yw==} + dev: true + + /broccoli-node-info@2.2.0: + resolution: {integrity: sha512-VabSGRpKIzpmC+r+tJueCE5h8k6vON7EIMMWu6d/FyPdtijwLQ7QvzShEw+m3mHoDzUaj/kiZsDYrS8X2adsBg==} + engines: {node: 8.* || >= 10.*} + dev: true + + /broccoli-output-wrapper@3.2.5: + resolution: {integrity: sha512-bQAtwjSrF4Nu0CK0JOy5OZqw9t5U0zzv2555EA/cF8/a8SLDTIetk9UgrtMVw7qKLKdSpOZ2liZNeZZDaKgayw==} + engines: {node: 10.* || >= 12.*} + dependencies: + fs-extra: 8.1.0 + heimdalljs-logger: 0.1.10 + symlink-or-copy: 1.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /broccoli-plugin@4.0.7: + resolution: {integrity: sha512-a4zUsWtA1uns1K7p9rExYVYG99rdKeGRymW0qOCNkvDPHQxVi3yVyJHhQbM3EZwdt2E0mnhr5e0c/bPpJ7p3Wg==} + engines: {node: 10.* || >= 12.*} + dependencies: + broccoli-node-api: 1.7.0 + broccoli-output-wrapper: 3.2.5 + fs-merger: 3.2.1 + promise-map-series: 0.3.0 + quick-temp: 0.1.8 + rimraf: 3.0.2 + symlink-or-copy: 1.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /browserslist@4.21.4: + resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001448 + electron-to-chromium: 1.4.284 + node-releases: 2.0.8 + update-browserslist-db: 1.0.10(browserslist@4.21.4) + dev: true + + /buffer-equal@1.0.1: + resolution: {integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==} + engines: {node: '>=0.4'} + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + /caniuse-lite@1.0.30001448: + resolution: {integrity: sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==} + dev: true + + /caniuse-lite@1.0.30001449: + resolution: {integrity: sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==} + dev: false + + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: false + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: false + + /character-entities@2.0.1: + resolution: {integrity: sha512-OzmutCf2Kmc+6DrFrrPS8/tDh2+DpnrfzdICHWhcVC9eOd0N1PXmQEE1a8iM4IziIAG+8tmTq3K+oo0ubH6RRQ==} + dev: false + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: false + + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + dev: true + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.0.1 + htmlparser2: 8.0.1 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: true + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + + /client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + dev: false + + /clipboard-copy@3.2.0: + resolution: {integrity: sha512-vooFaGFL6ulEP1liiaWFBmmfuPm3cY3y7T9eB83ZTnYc/oFeAKsq3NcDrOkBC8XaauEE8zHQwI7k0+JSYiVQSQ==} + dev: false + + /clone-buffer@1.0.0: + resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} + engines: {node: '>= 0.10'} + dev: true + + /clone-stats@1.0.0: + resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==} + dev: true + + /clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + dev: true + + /cloneable-readable@1.1.3: + resolution: {integrity: sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==} + dependencies: + inherits: 2.0.4 + process-nextick-args: 2.0.1 + readable-stream: 2.3.7 + dev: true + + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + dev: true + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /comma-separated-tokens@1.0.8: + resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + dev: false + + /comma-separated-tokens@2.0.2: + resolution: {integrity: sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==} + dev: false + + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + + /commander@10.0.0: + resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} + engines: {node: '>=14'} + dev: true + + /commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + dev: false + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.0 + typedarray: 0.0.6 + dev: true + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + + /copy-to-clipboard@3.3.1: + resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==} + dependencies: + toggle-selection: 1.0.6 + dev: false + + /core-js@3.27.2: + resolution: {integrity: sha512-9ashVQskuh5AZEZ1JdQWp1GqSoC1e1G87MzRqg2gIfVAQ7Qn9K+uFj8EcniUFA4P2NLZfV+TOlX1SzoKfo+s7w==} + requiresBuild: true + dev: false + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /crypto-js@4.1.1: + resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==} + dev: false + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.0.1 + nth-check: 2.1.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + /csstype@3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + + /custom-event-polyfill@1.0.7: + resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==} + dev: false + + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: false + + /damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dev: true + + /dayjs@1.11.7: + resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + dev: false + + /de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + dev: true + + /debounce-promise@3.1.2: + resolution: {integrity: sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==} + dev: false + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: true + + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decode-named-character-reference@1.0.1: + resolution: {integrity: sha512-YV/0HQHreRwKb7uBopyIkLG17jG6Sv2qUchk9qSoVJ2f+flwRsPNBO0hAnjt6mTNYUT+vw9Gy2ihXg4sUWPi2w==} + dependencies: + character-entities: 2.0.1 + dev: false + + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + dependencies: + character-entities: 2.0.2 + dev: false + + /deep-equal@2.2.0: + resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} + dependencies: + call-bind: 1.0.2 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.0 + is-arguments: 1.1.1 + is-array-buffer: 3.0.1 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.9 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + + /define-properties@1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /defined@1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false + + /dequal@2.0.2: + resolution: {integrity: sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==} + engines: {node: '>=6'} + dev: false + + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + + /detective@5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true + dependencies: + acorn-node: 1.8.2 + defined: 1.0.1 + minimist: 1.2.7 + + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + /diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + dev: false + + /diff@5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} + dev: false + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.4.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /dommatrix@1.0.3: + resolution: {integrity: sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==} + dev: true + + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + + /duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + dependencies: + end-of-stream: 1.4.4 + inherits: 2.0.4 + readable-stream: 2.3.7 + stream-shift: 1.0.1 + dev: true + + /electron-to-chromium@1.4.284: + resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} + dev: true + + /emoji-regex@10.2.1: + resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} + dev: false + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /enhanced-resolve@5.12.0: + resolution: {integrity: sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.10 + tapable: 2.2.1 + dev: true + + /ensure-posix-path@1.1.1: + resolution: {integrity: sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==} + dev: true + + /entities@4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + dev: true + + /eol@0.9.1: + resolution: {integrity: sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==} + dev: true + + /epubjs@0.3.93: + resolution: {integrity: sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==} + dependencies: + '@types/localforage': 0.0.34 + '@xmldom/xmldom': 0.7.9 + core-js: 3.27.2 + event-emitter: 0.3.5 + jszip: 3.10.1 + localforage: 1.10.0 + lodash: 4.17.21 + marks-pane: 1.0.9 + path-webpack: 0.0.3 + dev: false + + /es-abstract@1.21.1: + resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.0 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + is-array-buffer: 3.0.1 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.9 + dev: true + + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-shim-unscopables@1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: false + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: false + + /es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + dev: false + + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: false + + /esbuild@0.17.4: + resolution: {integrity: sha512-zBn9MeCwT7W5F1a3lXClD61ip6vQM+H8Msb0w8zMT4ZKBpDg+rFAraNyWCDelB/2L6M3g6AXHPnsyvjMFnxtFw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.4 + '@esbuild/android-arm64': 0.17.4 + '@esbuild/android-x64': 0.17.4 + '@esbuild/darwin-arm64': 0.17.4 + '@esbuild/darwin-x64': 0.17.4 + '@esbuild/freebsd-arm64': 0.17.4 + '@esbuild/freebsd-x64': 0.17.4 + '@esbuild/linux-arm': 0.17.4 + '@esbuild/linux-arm64': 0.17.4 + '@esbuild/linux-ia32': 0.17.4 + '@esbuild/linux-loong64': 0.17.4 + '@esbuild/linux-mips64el': 0.17.4 + '@esbuild/linux-ppc64': 0.17.4 + '@esbuild/linux-riscv64': 0.17.4 + '@esbuild/linux-s390x': 0.17.4 + '@esbuild/linux-x64': 0.17.4 + '@esbuild/netbsd-x64': 0.17.4 + '@esbuild/openbsd-x64': 0.17.4 + '@esbuild/sunos-x64': 0.17.4 + '@esbuild/win32-arm64': 0.17.4 + '@esbuild/win32-ia32': 0.17.4 + '@esbuild/win32-x64': 0.17.4 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false + + /eslint-config-next@13.4.7(eslint@8.43.0)(typescript@5.1.3): + resolution: {integrity: sha512-+IRAyD0+J1MZaTi9RQMPUfr6Q+GCZ1wOkK6XM52Vokh7VI4R6YFGOFzdkEFHl4ZyIX4FKa5vcwUP2WscSFNjNQ==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 13.4.7 + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/parser': 5.49.0(eslint@8.43.0)(typescript@5.1.3) + eslint: 8.43.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.3(eslint-plugin-import@2.27.5)(eslint@8.43.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.49.0)(eslint-import-resolver-typescript@3.5.3)(eslint@8.43.0) + eslint-plugin-jsx-a11y: 6.7.1(eslint@8.43.0) + eslint-plugin-react: 7.32.1(eslint@8.43.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.43.0) + typescript: 5.1.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-config-prettier@8.6.0(eslint@8.43.0): + resolution: {integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.43.0 + dev: true + + /eslint-import-resolver-node@0.3.7: + resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} + dependencies: + debug: 3.2.7 + is-core-module: 2.11.0 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-import-resolver-typescript@3.5.3(eslint-plugin-import@2.27.5)(eslint@8.43.0): + resolution: {integrity: sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + enhanced-resolve: 5.12.0 + eslint: 8.43.0 + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.49.0)(eslint-import-resolver-typescript@3.5.3)(eslint@8.43.0) + get-tsconfig: 4.3.0 + globby: 13.1.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + synckit: 0.8.4 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.49.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.3)(eslint@8.43.0): + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.49.0(eslint@8.43.0)(typescript@5.1.3) + debug: 3.2.7 + eslint: 8.43.0 + eslint-import-resolver-node: 0.3.7 + eslint-import-resolver-typescript: 3.5.3(eslint-plugin-import@2.27.5)(eslint@8.43.0) + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.49.0)(eslint-import-resolver-typescript@3.5.3)(eslint@8.43.0): + resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.49.0(eslint@8.43.0)(typescript@5.1.3) + array-includes: 3.1.6 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.43.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.49.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.3)(eslint@8.43.0) + has: 1.0.3 + is-core-module: 2.11.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.6 + resolve: 1.22.1 + semver: 6.3.0 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-jsx-a11y@6.7.1(eslint@8.43.0): + resolution: {integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.20.13 + aria-query: 5.1.3 + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + ast-types-flow: 0.0.7 + axe-core: 4.6.3 + axobject-query: 3.1.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.43.0 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + semver: 6.3.0 + dev: true + + /eslint-plugin-react-hooks@4.6.0(eslint@8.43.0): + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.43.0 + dev: true + + /eslint-plugin-react@7.32.1(eslint@8.43.0): + resolution: {integrity: sha512-vOjdgyd0ZHBXNsmvU+785xY8Bfe57EFbTYYk8XrROzWpr9QBvpjITvAXt9xqcE6+8cjR/g1+mfumPToxsl1www==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.6 + array.prototype.flatmap: 1.3.1 + array.prototype.tosorted: 1.1.1 + doctrine: 2.1.0 + eslint: 8.43.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.6 + object.fromentries: 2.0.6 + object.hasown: 1.1.2 + object.values: 1.1.6 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.8 + dev: true + + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.43.0: + resolution: {integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.2.0(eslint@8.43.0) + '@eslint-community/regexpp': 4.4.0 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.43.0 + '@humanwhocodes/config-array': 0.11.10 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 + esquery: 1.4.2 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.20.0 + graphemer: 1.4.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.5.2: + resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) + eslint-visitor-keys: 3.4.1 + dev: true + + /esquery@1.4.2: + resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: false + + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: false + + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-fifo@1.1.0: + resolution: {integrity: sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==} + dev: true + + /fast-glob@3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + dependencies: + format: 0.2.2 + dev: false + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /flush-write-stream@1.1.1: + resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==} + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.7 + dev: true + + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false + + /fraction.js@4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: true + + /fs-extra@11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs-merger@3.2.1: + resolution: {integrity: sha512-AN6sX12liy0JE7C2evclwoo0aCG3PFulLjrTLsJpWh/2mM+DinhpSGqYLbHBBbIW1PLRNcFhJG8Axtz8mQW3ug==} + dependencies: + broccoli-node-api: 1.7.0 + broccoli-node-info: 2.2.0 + fs-extra: 8.1.0 + fs-tree-diff: 2.0.1 + walk-sync: 2.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /fs-mkdirp-stream@1.0.0: + resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} + engines: {node: '>= 0.10'} + dependencies: + graceful-fs: 4.2.10 + through2: 2.0.5 + dev: true + + /fs-tree-diff@2.0.1: + resolution: {integrity: sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A==} + engines: {node: 6.* || 8.* || >= 10.*} + dependencies: + '@types/symlink-or-copy': 1.2.0 + heimdalljs-logger: 0.1.10 + object-assign: 4.1.1 + path-posix: 1.0.0 + symlink-or-copy: 1.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /get-intrinsic@1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + dev: true + + /get-tsconfig@4.3.0: + resolution: {integrity: sha512-YCcF28IqSay3fqpIu5y3Krg/utCBHBeoflkZyHj/QcqI2nrLPC3ZegS9CmIo+hJb8K7aiGsuUl7PwWVjNG2HQQ==} + dev: true + + /glob-parent@3.1.0: + resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==} + dependencies: + is-glob: 3.1.0 + path-dirname: 1.0.2 + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + + /glob-stream@6.1.0: + resolution: {integrity: sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==} + engines: {node: '>= 0.10'} + dependencies: + extend: 3.0.2 + glob: 7.2.3 + glob-parent: 3.1.0 + is-negated-glob: 1.0.0 + ordered-read-streams: 1.0.1 + pumpify: 1.5.1 + readable-stream: 2.3.7 + remove-trailing-separator: 1.1.0 + to-absolute-glob: 2.0.2 + unique-stream: 2.3.1 + dev: true + + /glob@7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals@13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.1.4 + dev: true + + /globalyzer@0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /globby@13.1.3: + resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + + /goober@2.1.11(csstype@3.1.1): + resolution: {integrity: sha512-5SS2lmxbhqH0u9ABEWq7WPU69a4i2pYcHeCxqaNq6Cw3mnrF0ghWNM4tEGid4dKy8XNIAUbuThuozDHHKJVh3A==} + peerDependencies: + csstype: ^3.0.10 + dependencies: + csstype: 3.1.1 + dev: false + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /gulp-sort@2.0.0: + resolution: {integrity: sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g==} + dependencies: + through2: 2.0.5 + dev: true + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hast-to-hyperscript@10.0.1: + resolution: {integrity: sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==} + dependencies: + '@types/unist': 2.0.6 + comma-separated-tokens: 2.0.2 + property-information: 6.1.1 + space-separated-tokens: 2.0.1 + style-to-object: 0.3.0 + unist-util-is: 5.1.1 + web-namespaces: 2.0.1 + dev: false + + /hast-util-from-parse5@7.1.0: + resolution: {integrity: sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==} + dependencies: + '@types/hast': 2.3.4 + '@types/parse5': 6.0.3 + '@types/unist': 2.0.6 + hastscript: 7.0.2 + property-information: 6.1.1 + vfile: 5.3.4 + vfile-location: 4.0.1 + web-namespaces: 2.0.1 + dev: false + + /hast-util-is-element@2.1.2: + resolution: {integrity: sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==} + dependencies: + '@types/hast': 2.3.4 + '@types/unist': 2.0.6 + dev: false + + /hast-util-parse-selector@2.2.5: + resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + dev: false + + /hast-util-parse-selector@3.1.0: + resolution: {integrity: sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==} + dependencies: + '@types/hast': 2.3.4 + dev: false + + /hast-util-raw@7.2.2: + resolution: {integrity: sha512-0x3BhhdlBcqRIKyc095lBSDvmQNMY3Eulj2PLsT5XCyKYrxssI5yr3P4Kv/PBo1s/DMkZy2voGkMXECnFCZRLQ==} + dependencies: + '@types/hast': 2.3.4 + '@types/parse5': 6.0.3 + hast-util-from-parse5: 7.1.0 + hast-util-to-parse5: 7.0.0 + html-void-elements: 2.0.1 + parse5: 6.0.1 + unist-util-position: 4.0.3 + unist-util-visit: 4.1.0 + vfile: 5.3.4 + web-namespaces: 2.0.1 + zwitch: 2.0.2 + dev: false + + /hast-util-to-parse5@7.0.0: + resolution: {integrity: sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==} + dependencies: + '@types/hast': 2.3.4 + '@types/parse5': 6.0.3 + hast-to-hyperscript: 10.0.1 + property-information: 6.1.1 + web-namespaces: 2.0.1 + zwitch: 2.0.2 + dev: false + + /hast-util-to-text@3.1.1: + resolution: {integrity: sha512-7S3mOBxACy8syL45hCn3J7rHqYaXkxRfsX6LXEU5Shz4nt4GxdjtMUtG+T6G/ZLUHd7kslFAf14kAN71bz30xA==} + dependencies: + '@types/hast': 2.3.4 + hast-util-is-element: 2.1.2 + unist-util-find-after: 4.0.0 + dev: false + + /hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + dev: false + + /hastscript@6.0.0: + resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + dependencies: + '@types/hast': 2.3.4 + comma-separated-tokens: 1.0.8 + hast-util-parse-selector: 2.2.5 + property-information: 5.6.0 + space-separated-tokens: 1.1.5 + dev: false + + /hastscript@7.0.2: + resolution: {integrity: sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==} + dependencies: + '@types/hast': 2.3.4 + comma-separated-tokens: 2.0.2 + hast-util-parse-selector: 3.1.0 + property-information: 6.1.1 + space-separated-tokens: 2.0.1 + dev: false + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /heimdalljs-logger@0.1.10: + resolution: {integrity: sha512-pO++cJbhIufVI/fmB/u2Yty3KJD0TqNPecehFae0/eps0hkZ3b4Zc/PezUMOpYuHFQbA7FxHZxa305EhmjLj4g==} + dependencies: + debug: 2.6.9 + heimdalljs: 0.2.6 + transitivePeerDependencies: + - supports-color + dev: true + + /heimdalljs@0.2.6: + resolution: {integrity: sha512-o9bd30+5vLBvBtzCPwwGqpry2+n0Hi6H1+qwt6y+0kwRHGGF8TFIhJPmnuM0xO97zaKrDZMwO/V56fAnn8m/tA==} + dependencies: + rsvp: 3.2.1 + dev: true + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + + /html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + + /html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} + dev: false + + /htmlparser2@8.0.1: + resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + entities: 4.4.0 + dev: true + + /i18next-fs-backend@2.1.1: + resolution: {integrity: sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA==} + dev: false + + /i18next-parser@7.6.0: + resolution: {integrity: sha512-NpBhaPzQXo9mo5nBP/eniOgUOlbDEd6WV7oEslUz3cxM8vClCz4c09iJ16tWUOTqbqSwoN2qhU2Y8w23SMwSnw==} + engines: {node: ^14.13.1 || >=16.0.0 || >=18.0.0, npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + '@babel/runtime': 7.20.13 + broccoli-plugin: 4.0.7 + cheerio: 1.0.0-rc.12 + colors: 1.4.0 + commander: 10.0.0 + concat-stream: 2.0.0 + eol: 0.9.1 + esbuild: 0.17.4 + fs-extra: 11.1.0 + gulp-sort: 2.0.0 + i18next: 22.4.9 + js-yaml: 4.1.0 + lilconfig: 2.0.6 + rsvp: 4.8.5 + sort-keys: 5.0.0 + through2: 4.0.2 + typescript: 4.9.5 + vinyl: 3.0.0 + vinyl-fs: 3.0.3 + vue-template-compiler: 2.7.14 + transitivePeerDependencies: + - supports-color + dev: true + + /i18next@22.4.9: + resolution: {integrity: sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==} + dependencies: + '@babel/runtime': 7.20.13 + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + dev: false + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + dev: false + + /internal-slot@1.0.4: + resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /ioredis@5.3.0: + resolution: {integrity: sha512-Id9jKHhsILuIZpHc61QkagfVdUj2Rag5GzG1TGEvRNeM7dtTOjICgjC+tvqYxi//PuX2wjQ+Xjva2ONBuf92Pw==} + engines: {node: '>=12.22.0'} + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.4 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + + /is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + dev: true + + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: false + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: false + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-array-buffer@3.0.1: + resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-typed-array: 1.1.10 + dev: true + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: true + + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + dev: false + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module@2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: false + + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-glob@3.1.0: + resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: false + + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + + /is-negated-glob@1.0.0: + resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==} + engines: {node: '>=0.10.0'} + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj@4.0.0: + resolution: {integrity: sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==} + engines: {node: '>=12'} + dev: false + + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + dependencies: + is-unc-path: 1.0.0 + dev: true + + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array@1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + dependencies: + unc-path-regex: 0.1.2 + dev: true + + /is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + dev: true + + /is-valid-glob@1.0.0: + resolution: {integrity: sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + dev: true + + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + dev: true + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + dependencies: + minimist: 1.2.7 + dev: true + + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsx-ast-utils@3.3.3: + resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.6 + object.assign: 4.1.4 + dev: true + + /jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.7 + setimmediate: 1.0.5 + dev: false + + /katex@0.13.24: + resolution: {integrity: sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: false + + /katex@0.15.2: + resolution: {integrity: sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ==} + hasBin: true + dependencies: + commander: 8.3.0 + dev: false + + /kleur@4.1.4: + resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==} + engines: {node: '>=6'} + dev: false + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: false + + /language-subtag-registry@0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + dev: true + + /language-tags@1.0.5: + resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} + dependencies: + language-subtag-registry: 0.3.22 + dev: true + + /lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + dependencies: + readable-stream: 2.3.7 + dev: true + + /lead@1.0.0: + resolution: {integrity: sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==} + engines: {node: '>= 0.10'} + dependencies: + flush-write-stream: 1.1.1 + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + dependencies: + immediate: 3.0.6 + dev: false + + /lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + dependencies: + immediate: 3.0.6 + dev: false + + /lilconfig@2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + + /loadjs@4.2.0: + resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==} + dev: false + + /localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + dependencies: + lie: 3.1.1 + dev: false + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /longest-streak@3.0.1: + resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==} + dev: false + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + + /lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + dev: false + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /markdown-table@3.0.2: + resolution: {integrity: sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==} + dev: false + + /marks-pane@1.0.9: + resolution: {integrity: sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg==} + dev: false + + /matcher-collection@2.0.1: + resolution: {integrity: sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==} + engines: {node: 6.* || 8.* || >= 10.*} + dependencies: + '@types/minimatch': 3.0.5 + minimatch: 3.1.2 + dev: true + + /mdast-util-definitions@5.1.1: + resolution: {integrity: sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==} + dependencies: + '@types/mdast': 3.0.10 + '@types/unist': 2.0.6 + unist-util-visit: 4.1.2 + dev: false + + /mdast-util-find-and-replace@2.1.0: + resolution: {integrity: sha512-1w1jbqAd13oU78QPBf5223+xB+37ecNtQ1JElq2feWols5oEYAl+SgNDnOZipe7NfLemoEt362yUS15/wip4mw==} + dependencies: + escape-string-regexp: 5.0.0 + unist-util-is: 5.1.1 + unist-util-visit-parents: 4.1.1 + dev: false + + /mdast-util-from-markdown@1.2.0: + resolution: {integrity: sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==} + dependencies: + '@types/mdast': 3.0.10 + '@types/unist': 2.0.6 + decode-named-character-reference: 1.0.2 + mdast-util-to-string: 3.1.1 + micromark: 3.1.0 + micromark-util-decode-numeric-character-reference: 1.0.0 + micromark-util-decode-string: 1.0.2 + micromark-util-normalize-identifier: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-autolink-literal@1.0.2: + resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==} + dependencies: + '@types/mdast': 3.0.10 + ccount: 2.0.1 + mdast-util-find-and-replace: 2.1.0 + micromark-util-character: 1.1.0 + dev: false + + /mdast-util-gfm-footnote@1.0.1: + resolution: {integrity: sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + micromark-util-normalize-identifier: 1.0.0 + dev: false + + /mdast-util-gfm-strikethrough@1.0.1: + resolution: {integrity: sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + dev: false + + /mdast-util-gfm-table@1.0.3: + resolution: {integrity: sha512-B/tgpJjND1qIZM2WZst+NYnb0notPE6m0J+YOe3NOHXyEmvK38ytxaOsgz4BvrRPQQcNbRrTzSHMPnBkj1fCjg==} + dependencies: + markdown-table: 3.0.2 + mdast-util-to-markdown: 1.3.0 + dev: false + + /mdast-util-gfm-task-list-item@1.0.1: + resolution: {integrity: sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + dev: false + + /mdast-util-gfm@2.0.0: + resolution: {integrity: sha512-wMwejlTN3EQADPFuvxe8lmGsay3+f6gSJKdAHR6KBJzpcxvsjJSILB9K6u6G7eQLC7iOTyVIHYGui9uBc9r1Tg==} + dependencies: + mdast-util-gfm-autolink-literal: 1.0.2 + mdast-util-gfm-footnote: 1.0.1 + mdast-util-gfm-strikethrough: 1.0.1 + mdast-util-gfm-table: 1.0.3 + mdast-util-gfm-task-list-item: 1.0.1 + dev: false + + /mdast-util-math@2.0.1: + resolution: {integrity: sha512-ZZtjyRwobsiVg4bY0Q5CzAZztpbjRIA7ZlMMb0PNkwTXOnJTUoHvzBhVG95LIuek5Mlj1l2P+jBvWviqW7G+0A==} + dependencies: + '@types/mdast': 3.0.10 + longest-streak: 3.0.1 + mdast-util-to-markdown: 1.3.0 + dev: false + + /mdast-util-to-hast@12.2.6: + resolution: {integrity: sha512-Kfo1JNUsi6r6CI7ZOJ6yt/IEKMjMK4nNjQ8C+yc8YBbIlDSp9dmj0zY90ryiu6Vy4CVGv0zi1H4ZoIaYVV8cwA==} + dependencies: + '@types/hast': 2.3.4 + '@types/mdast': 3.0.10 + mdast-util-definitions: 5.1.1 + micromark-util-sanitize-uri: 1.1.0 + trim-lines: 3.0.1 + unist-builder: 3.0.1 + unist-util-generated: 2.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + dev: false + + /mdast-util-to-markdown@1.3.0: + resolution: {integrity: sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==} + dependencies: + '@types/mdast': 3.0.10 + '@types/unist': 2.0.6 + longest-streak: 3.0.1 + mdast-util-to-string: 3.1.0 + micromark-util-decode-string: 1.0.2 + unist-util-visit: 4.1.0 + zwitch: 2.0.2 + dev: false + + /mdast-util-to-string@3.1.0: + resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==} + dev: false + + /mdast-util-to-string@3.1.1: + resolution: {integrity: sha512-tGvhT94e+cVnQt8JWE9/b3cUQZWS732TJxXHktvP+BYo62PpYD53Ls/6cC60rW21dW+txxiM4zMdc6abASvZKA==} + dependencies: + '@types/mdast': 3.0.10 + dev: false + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /micromark-core-commonmark@1.0.6: + resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} + dependencies: + decode-named-character-reference: 1.0.1 + micromark-factory-destination: 1.0.0 + micromark-factory-label: 1.0.2 + micromark-factory-space: 1.0.0 + micromark-factory-title: 1.0.2 + micromark-factory-whitespace: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-chunked: 1.0.0 + micromark-util-classify-character: 1.0.0 + micromark-util-html-tag-name: 1.0.0 + micromark-util-normalize-identifier: 1.0.0 + micromark-util-resolve-all: 1.0.0 + micromark-util-subtokenize: 1.0.2 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm-autolink-literal@1.0.3: + resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-sanitize-uri: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm-footnote@1.0.3: + resolution: {integrity: sha512-bn62pC5y39rIo2g1RqZk1NhF7T7cJLuJlbevunQz41U0iPVCdVOFASe5/L1kke+DFKSgfCRhv24+o42cZ1+ADw==} + dependencies: + micromark-core-commonmark: 1.0.6 + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-normalize-identifier: 1.0.0 + micromark-util-sanitize-uri: 1.0.0 + micromark-util-symbol: 1.0.1 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm-strikethrough@1.0.4: + resolution: {integrity: sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==} + dependencies: + micromark-util-chunked: 1.0.0 + micromark-util-classify-character: 1.0.0 + micromark-util-resolve-all: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm-table@1.0.5: + resolution: {integrity: sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm-tagfilter@1.0.1: + resolution: {integrity: sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==} + dependencies: + micromark-util-types: 1.0.2 + dev: false + + /micromark-extension-gfm-task-list-item@1.0.3: + resolution: {integrity: sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-extension-gfm@2.0.1: + resolution: {integrity: sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==} + dependencies: + micromark-extension-gfm-autolink-literal: 1.0.3 + micromark-extension-gfm-footnote: 1.0.3 + micromark-extension-gfm-strikethrough: 1.0.4 + micromark-extension-gfm-table: 1.0.5 + micromark-extension-gfm-tagfilter: 1.0.1 + micromark-extension-gfm-task-list-item: 1.0.3 + micromark-util-combine-extensions: 1.0.0 + micromark-util-types: 1.0.2 + dev: false + + /micromark-extension-math@2.0.2: + resolution: {integrity: sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA==} + dependencies: + '@types/katex': 0.11.1 + katex: 0.13.24 + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-factory-destination@1.0.0: + resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + dev: false + + /micromark-factory-label@1.0.2: + resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-factory-space@1.0.0: + resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-types: 1.0.2 + dev: false + + /micromark-factory-title@1.0.2: + resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-factory-whitespace@1.0.0: + resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + dev: false + + /micromark-util-character@1.1.0: + resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==} + dependencies: + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + dev: false + + /micromark-util-chunked@1.0.0: + resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==} + dependencies: + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-classify-character@1.0.0: + resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + dev: false + + /micromark-util-combine-extensions@1.0.0: + resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==} + dependencies: + micromark-util-chunked: 1.0.0 + micromark-util-types: 1.0.2 + dev: false + + /micromark-util-decode-numeric-character-reference@1.0.0: + resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==} + dependencies: + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-decode-string@1.0.2: + resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==} + dependencies: + decode-named-character-reference: 1.0.1 + micromark-util-character: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.0.0 + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-encode@1.0.1: + resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==} + dev: false + + /micromark-util-html-tag-name@1.0.0: + resolution: {integrity: sha512-NenEKIshW2ZI/ERv9HtFNsrn3llSPZtY337LID/24WeLqMzeZhBEE6BQ0vS2ZBjshm5n40chKtJ3qjAbVV8S0g==} + dev: false + + /micromark-util-normalize-identifier@1.0.0: + resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==} + dependencies: + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-resolve-all@1.0.0: + resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==} + dependencies: + micromark-util-types: 1.0.2 + dev: false + + /micromark-util-sanitize-uri@1.0.0: + resolution: {integrity: sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-encode: 1.0.1 + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-sanitize-uri@1.1.0: + resolution: {integrity: sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-encode: 1.0.1 + micromark-util-symbol: 1.0.1 + dev: false + + /micromark-util-subtokenize@1.0.2: + resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==} + dependencies: + micromark-util-chunked: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.3 + dev: false + + /micromark-util-symbol@1.0.1: + resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==} + dev: false + + /micromark-util-types@1.0.2: + resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==} + dev: false + + /micromark@3.1.0: + resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==} + dependencies: + '@types/debug': 4.1.7 + debug: 4.3.4 + decode-named-character-reference: 1.0.2 + micromark-core-commonmark: 1.0.6 + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-chunked: 1.0.0 + micromark-util-combine-extensions: 1.0.0 + micromark-util-decode-numeric-character-reference: 1.0.0 + micromark-util-encode: 1.0.1 + micromark-util-normalize-identifier: 1.0.0 + micromark-util-resolve-all: 1.0.0 + micromark-util-sanitize-uri: 1.1.0 + micromark-util-subtokenize: 1.0.2 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + dev: false + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + + /mktemp@0.4.0: + resolution: {integrity: sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A==} + engines: {node: '>0.9'} + dev: true + + /mpegts.js@1.7.2: + resolution: {integrity: sha512-qQ1ELBDC4IAqpULFuFzp3hoQeKwD5BCR3UM9Lk2+kj9jCWcXl19spF7PdzX0ZljghPHAj/VL2ajBbGyMWk2fgA==} + dependencies: + es6-promise: 4.2.8 + webworkify-webpack: 2.1.5 + dev: false + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: false + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid@3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /next-i18next@13.0.3(i18next@22.4.9)(next@13.1.6)(react-i18next@12.1.4)(react@18.2.0): + resolution: {integrity: sha512-7AA8J6WbkxRBtSf1+97LSAE7btxWZHsBIJEJ3FuTSBgYtpRiO5NGjcb8XbNAlz6yGU0TtS+yZE+/Wu83KhIT1Q==} + engines: {node: '>=14'} + peerDependencies: + i18next: ^22.0.6 + next: '>= 12.0.0' + react: '>= 17.0.2' + react-i18next: ^12.1.1 + dependencies: + '@babel/runtime': 7.20.13 + '@types/hoist-non-react-statics': 3.3.1 + core-js: 3.27.2 + hoist-non-react-statics: 3.3.2 + i18next: 22.4.9 + i18next-fs-backend: 2.1.1 + next: 13.1.6(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-i18next: 12.1.4(i18next@22.4.9)(react-dom@18.2.0)(react@18.2.0) + dev: false + + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: false + + /next@13.1.6(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-hHlbhKPj9pW+Cymvfzc15lvhaOZ54l+8sXDXJWm3OBNBzgrVj6hwGPmqqsXg40xO1Leq+kXpllzRPuncpC0Phw==} + engines: {node: '>=14.6.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 13.1.6 + '@swc/helpers': 0.4.14 + caniuse-lite: 1.0.30001449 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + styled-jsx: 5.1.1(react@18.2.0) + optionalDependencies: + '@next/swc-android-arm-eabi': 13.1.6 + '@next/swc-android-arm64': 13.1.6 + '@next/swc-darwin-arm64': 13.1.6 + '@next/swc-darwin-x64': 13.1.6 + '@next/swc-freebsd-x64': 13.1.6 + '@next/swc-linux-arm-gnueabihf': 13.1.6 + '@next/swc-linux-arm64-gnu': 13.1.6 + '@next/swc-linux-arm64-musl': 13.1.6 + '@next/swc-linux-x64-gnu': 13.1.6 + '@next/swc-linux-x64-musl': 13.1.6 + '@next/swc-win32-arm64-msvc': 13.1.6 + '@next/swc-win32-ia32-msvc': 13.1.6 + '@next/swc-win32-x64-msvc': 13.1.6 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + + /nextjs-progressbar@0.0.16(next@13.1.6)(react@18.2.0): + resolution: {integrity: sha512-GV0fD38EMD3vSDCmkq+tObmoup6QA91a6a9MxGuhJZuRk/9TNsrHGnIQQQ/sggkMkXuT4fBgF6jRjFwScDT3zA==} + peerDependencies: + next: '>= 6.0.0' + react: '>= 16.0.0' + dependencies: + '@types/nprogress': 0.2.0 + next: 13.1.6(react-dom@18.2.0)(react@18.2.0) + nprogress: 0.2.0 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /node-releases@2.0.8: + resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} + dev: true + + /normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + dependencies: + remove-trailing-separator: 1.1.0 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + + /now-and-later@2.0.1: + resolution: {integrity: sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==} + engines: {node: '>= 0.10'} + dependencies: + once: 1.4.0 + dev: true + + /nprogress@0.2.0: + resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + dev: false + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.entries@1.1.6: + resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /object.fromentries@2.0.6: + resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /object.hasown@1.1.2: + resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} + dependencies: + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /object.values@1.1.6: + resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /open@8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + + /optionator@0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /ordered-read-streams@1.0.1: + resolution: {integrity: sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==} + dependencies: + readable-stream: 2.3.7 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: false + + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: true + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.4.0 + dev: true + + /path-dirname@1.0.2: + resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-posix@1.0.0: + resolution: {integrity: sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==} + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /path-webpack@0.0.3: + resolution: {integrity: sha1-/23sdJ7sWpRgXATV9j/FVgegOhY=} + dev: false + + /pdfjs-dist@2.16.105: + resolution: {integrity: sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==} + peerDependencies: + worker-loader: ^3.0.8 + peerDependenciesMeta: + worker-loader: + optional: true + dependencies: + dommatrix: 1.0.3 + web-streams-polyfill: 3.2.1 + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + /plyr-react@5.1.2(plyr@3.7.3)(react@18.2.0): + resolution: {integrity: sha512-RHRlK5wKIRIpX0xHmnHcGJGH2hFAU7njHrIE2bv/8J2l9BjmCM7KOaRKMQYPxkpCNXGtdVgAt3qYitegRN7g9g==} + engines: {node: '>=12.7.0'} + peerDependencies: + plyr: ^3.7.3 + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + dependencies: + plyr: 3.7.3 + react: 18.2.0 + react-aptor: 2.0.0(react@18.2.0) + dev: false + + /plyr@3.7.3: + resolution: {integrity: sha512-ORULENBvEvvzMYXRQBALDmEi8P+wZt1Hr/NvHqchu/t7E2xJKNkRYWx0qCA1HETIGZ6zobrOVgqeAUqWimS7fQ==} + dependencies: + core-js: 3.27.2 + custom-event-polyfill: 1.0.7 + loadjs: 4.2.0 + rangetouch: 2.0.1 + url-polyfill: 1.1.12 + dev: false + + /postcss-import@14.1.0(postcss@8.4.21): + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.1 + + /postcss-js@4.0.0(postcss@8.4.21): + resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.3.3 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.21 + + /postcss-load-config@3.1.4(postcss@8.4.21): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.6 + postcss: 8.4.21 + yaml: 1.10.2 + + /postcss-nested@6.0.0(postcss@8.4.21): + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.21 + postcss-selector-parser: 6.0.11 + + /postcss-selector-parser@6.0.11: + resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + /postcss@8.4.14: + resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-plugin-tailwindcss@0.2.2(prettier@2.8.3): + resolution: {integrity: sha512-5RjUbWRe305pUpc48MosoIp6uxZvZxrM6GyOgsbGLTce+ehePKNm7ziW2dLG2air9aXbGuXlHVSQQw4Lbosq3w==} + engines: {node: '>=12.17.0'} + peerDependencies: + '@prettier/plugin-php': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@shufo/prettier-plugin-blade': '*' + '@trivago/prettier-plugin-sort-imports': '*' + prettier: '>=2.2.0' + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + prettier-plugin-twig-melody: '*' + peerDependenciesMeta: + '@prettier/plugin-php': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@shufo/prettier-plugin-blade': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + prettier-plugin-twig-melody: + optional: true + dependencies: + prettier: 2.8.3 + dev: true + + /prettier@2.8.3: + resolution: {integrity: sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /preview-office-docs@1.0.2(react@18.2.0): + resolution: {integrity: sha512-dVss4jWfVgZJcVskbOPV5ifpFokRYY1ZW54Z6DvwCg/HzNqq8rZw8yVM+nJAFmcFz6Zy7aY9n1/KhPDkm+Ky6Q==} + engines: {node: '>=10'} + peerDependencies: + react: ^16.0.0 + dependencies: + react: 18.2.0 + dev: false + + /prismjs@1.27.0: + resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} + engines: {node: '>=6'} + dev: false + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: false + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + /promise-map-series@0.3.0: + resolution: {integrity: sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA==} + engines: {node: 10.* || >= 12.*} + dev: true + + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + /property-information@5.6.0: + resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + dependencies: + xtend: 4.0.2 + dev: false + + /property-information@6.1.1: + resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==} + dev: false + + /property-information@6.2.0: + resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} + dev: false + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /pumpify@1.5.1: + resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + dependencies: + duplexify: 3.7.1 + inherits: 2.0.4 + pump: 2.0.1 + dev: true + + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: true + + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + /quick-temp@0.1.8: + resolution: {integrity: sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==} + dependencies: + mktemp: 0.4.0 + rimraf: 2.7.1 + underscore.string: 3.3.6 + dev: true + + /rangetouch@2.0.1: + resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==} + dev: false + + /react-aptor@2.0.0(react@18.2.0): + resolution: {integrity: sha512-YnCayokuhAwmBBP4Oc0bbT2l6ApfsjbY3DEEVUddIKZEBlGl1npzjHHzWnSqWuboSbMZvRqUM01Io9yiIp1wcg==} + engines: {node: '>=12.7.0'} + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + dependencies: + react: 18.2.0 + dev: false + + /react-async-hook@4.0.0(react@18.2.0): + resolution: {integrity: sha512-97lgjFkOcHCTYSrsKBpsXg3iVWM0LnzedB749iP76sb3/8Ouu4nHIkCLEOrQWHVYqrYxjF05NN6GHoXWFkB3Kw==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: '>=16.8' + dependencies: + react: 18.2.0 + dev: false + + /react-audio-player@0.17.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aCZgusPxA9HK7rLZcTdhTbBH9l6do9vn3NorgoDZRxRxJlOy9uZWzPaKjd7QdcuP2vXpxGA/61JMnnOEY7NXeA==} + peerDependencies: + react: '>=16' + react-dom: '>=16' + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-cookie@4.1.1(react@18.2.0): + resolution: {integrity: sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==} + peerDependencies: + react: '>= 16.3.0' + dependencies: + '@types/hoist-non-react-statics': 3.3.1 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + universal-cookie: 4.0.4 + dev: false + + /react-copy-to-clipboard@5.1.0(react@18.2.0): + resolution: {integrity: sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==} + peerDependencies: + react: ^15.3.0 || 16 || 17 || 18 + dependencies: + copy-to-clipboard: 3.3.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react-hot-toast@2.4.0(csstype@3.1.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16' + react-dom: '>=16' + dependencies: + goober: 2.1.11(csstype@3.1.1) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - csstype + dev: false + + /react-hotkeys-hook@4.3.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-OYZCG2G+xLeiH0TkrW+v6eKFPvYq9iCA5sh9pwvnbGQaK86Lw/kWJDjjzSksFJoCk4K78OLe3MR1afNZH6+cLg==} + peerDependencies: + react: '>=16.8.1' + react-dom: '>=16.8.1' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-i18next@12.1.4(i18next@22.4.9)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XQND7jYtgM7ht5PH3yIZljCRpAMTlH/zmngM9ZjToqa+0BR6xuu8c7QF0WIIOEjcMTB2S3iOfpN/xG/ZrAnO6g==} + peerDependencies: + i18next: '>= 19.0.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.20.13 + html-parse-stringify: 3.0.1 + i18next: 22.4.9 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false + + /react-markdown@8.0.5(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + dependencies: + '@types/hast': 2.3.4 + '@types/prop-types': 15.7.5 + '@types/react': 18.2.14 + '@types/unist': 2.0.6 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 2.0.1 + prop-types: 15.8.1 + property-information: 6.2.0 + react: 18.2.0 + react-is: 18.2.0 + remark-parse: 10.0.1 + remark-rehype: 10.1.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.1 + unified: 10.1.2 + unist-util-visit: 4.1.2 + vfile: 5.3.6 + transitivePeerDependencies: + - supports-color + dev: false + + /react-reader@1.0.2(react@18.2.0): + resolution: {integrity: sha512-iN0yyIDF5GsdDpo1S2HApc7hTrra77nMHeTi8QDt4xGpFMWwMO1ZQmQH44poSE0GfYy3lkMoJYXZBg3vMKmIag==} + peerDependencies: + react: '>=17.0.2' + dependencies: + epubjs: 0.3.93 + prop-types: 15.8.1 + react: 18.2.0 + react-swipeable: 7.0.0(react@18.2.0) + dev: false + + /react-swipeable@7.0.0(react@18.2.0): + resolution: {integrity: sha512-NI7KGfQ6gwNFN0Hor3vytYW3iRfMMaivGEuxcADOOfBCx/kqwXE8IfHFxEcxSUkxCYf38COLKYd9EMYZghqaUA==} + peerDependencies: + react: ^16.8.3 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + + /react-syntax-highlighter@15.5.0(react@18.2.0): + resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} + peerDependencies: + react: '>= 0.14.0' + dependencies: + '@babel/runtime': 7.19.0 + highlight.js: 10.7.3 + lowlight: 1.20.0 + prismjs: 1.29.0 + react: 18.2.0 + refractor: 3.6.0 + dev: false + + /react-use-system-theme@1.1.1(react@18.2.0): + resolution: {integrity: sha512-Mlohol/QkEIbIKt8lXJP0iVvRAgOMml2iZ+bwvE1KpQFAG5DKJGFjZmEqebdj7WCu24u+34kpMtFDGMb4+5uSA==} + peerDependencies: + react: ^16.12.0 + dependencies: + react: 18.2.0 + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + + /readable-stream@2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + /readable-stream@3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + + /redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + dev: false + + /redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + dependencies: + redis-errors: 1.2.0 + dev: false + + /refractor@3.6.0: + resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + dependencies: + hastscript: 6.0.0 + parse-entities: 2.0.0 + prismjs: 1.27.0 + dev: false + + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + /regenerator-runtime@0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + dev: false + + /regexp.prototype.flags@1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + dev: true + + /rehype-katex@6.0.2: + resolution: {integrity: sha512-C4gDAlS1+l0hJqctyiU64f9CvT00S03qV1T6HiMzbSuLBgWUtcqydWHY9OpKrm0SpkK16FNd62CDKyWLwV2ppg==} + dependencies: + '@types/hast': 2.3.4 + '@types/katex': 0.11.1 + hast-util-to-text: 3.1.1 + katex: 0.15.2 + rehype-parse: 8.0.4 + unified: 10.1.1 + unist-util-remove-position: 4.0.1 + unist-util-visit: 4.1.0 + dev: false + + /rehype-parse@8.0.4: + resolution: {integrity: sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg==} + dependencies: + '@types/hast': 2.3.4 + hast-util-from-parse5: 7.1.0 + parse5: 6.0.1 + unified: 10.1.1 + dev: false + + /rehype-raw@6.1.1: + resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==} + dependencies: + '@types/hast': 2.3.4 + hast-util-raw: 7.2.2 + unified: 10.1.2 + dev: false + + /remark-gfm@3.0.1: + resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-gfm: 2.0.0 + micromark-extension-gfm: 2.0.1 + unified: 10.1.1 + dev: false + + /remark-math@5.1.1: + resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-math: 2.0.1 + micromark-extension-math: 2.0.2 + unified: 10.1.1 + dev: false + + /remark-parse@10.0.1: + resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-from-markdown: 1.2.0 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + dependencies: + '@types/hast': 2.3.4 + '@types/mdast': 3.0.10 + mdast-util-to-hast: 12.2.6 + unified: 10.1.2 + dev: false + + /remove-bom-buffer@3.0.0: + resolution: {integrity: sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==} + engines: {node: '>=0.10.0'} + dependencies: + is-buffer: 1.1.6 + is-utf8: 0.2.1 + dev: true + + /remove-bom-stream@1.2.0: + resolution: {integrity: sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==} + engines: {node: '>= 0.10'} + dependencies: + remove-bom-buffer: 3.0.0 + safe-buffer: 5.2.1 + through2: 2.0.5 + dev: true + + /remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + dev: true + + /replace-ext@1.0.1: + resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==} + engines: {node: '>= 0.10'} + dev: true + + /replace-ext@2.0.0: + resolution: {integrity: sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==} + engines: {node: '>= 10'} + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-options@1.1.0: + resolution: {integrity: sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==} + engines: {node: '>= 0.10'} + dependencies: + value-or-function: 3.0.0 + dev: true + + /resolve@1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /resolve@2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rsvp@3.2.1: + resolution: {integrity: sha512-Rf4YVNYpKjZ6ASAmibcwTNciQ5Co5Ztq6iZPEykHpkoflnD/K5ryE/rHehFsTm4NJj8nKDhbi3eKBWGogmNnkg==} + dev: true + + /rsvp@4.8.5: + resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} + engines: {node: 6.* || >= 7.*} + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: false + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-regex: 1.1.4 + dev: true + + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /semver@6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: true + + /semver@7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + + /sort-keys@5.0.0: + resolution: {integrity: sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==} + engines: {node: '>=12'} + dependencies: + is-plain-obj: 4.1.0 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + dev: false + + /space-separated-tokens@2.0.1: + resolution: {integrity: sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==} + dev: false + + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + + /sprintf-js@1.1.2: + resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + dev: true + + /standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false + + /stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.4 + dev: true + + /stream-shift@1.0.1: + resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} + dev: true + + /streamx@2.13.2: + resolution: {integrity: sha512-+TWqixPhGDXEG9L/XczSbhfkmwAtGs3BJX5QNU6cvno+pOLKeszByWcnaTu6dg8efsTYqR8ZZuXWHhZfgrxMvA==} + dependencies: + fast-fifo: 1.1.0 + queue-tick: 1.0.1 + dev: true + + /string.prototype.matchall@4.0.8: + resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + get-intrinsic: 1.2.0 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /style-to-object@0.3.0: + resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} + dependencies: + inline-style-parser: 0.1.1 + dev: false + + /style-to-object@0.4.1: + resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==} + dependencies: + inline-style-parser: 0.1.1 + dev: false + + /styled-jsx@5.1.1(react@18.2.0): + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + client-only: 0.0.1 + react: 18.2.0 + dev: false + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /swr@2.0.3(react@18.2.0): + resolution: {integrity: sha512-sGvQDok/AHEWTPfhUWXEHBVEXmgGnuahyhmRQbjl9XBYxT/MSlAzvXEKQpyM++bMPaI52vcWS2HiKNaW7+9OFw==} + engines: {pnpm: '7'} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + + /symlink-or-copy@1.3.1: + resolution: {integrity: sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==} + dev: true + + /synckit@0.8.4: + resolution: {integrity: sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/utils': 2.3.1 + tslib: 2.5.0 + dev: true + + /tailwindcss@3.2.4(postcss@8.4.21): + resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + detective: 5.2.1 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + lilconfig: 2.0.6 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-import: 14.1.0(postcss@8.4.21) + postcss-js: 4.0.0(postcss@8.4.21) + postcss-load-config: 3.1.4(postcss@8.4.21) + postcss-nested: 6.0.0(postcss@8.4.21) + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.1 + transitivePeerDependencies: + - ts-node + + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + + /teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + dependencies: + streamx: 2.13.2 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /through2-filter@3.0.0: + resolution: {integrity: sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==} + dependencies: + through2: 2.0.5 + xtend: 4.0.2 + dev: true + + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.7 + xtend: 4.0.2 + dev: true + + /through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /tiny-glob@0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: true + + /to-absolute-glob@2.0.2: + resolution: {integrity: sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==} + engines: {node: '>=0.10.0'} + dependencies: + is-absolute: 1.0.0 + is-negated-glob: 1.0.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /to-through@2.0.0: + resolution: {integrity: sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==} + engines: {node: '>= 0.10'} + dependencies: + through2: 2.0.5 + dev: true + + /toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + dev: false + + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.0.2: + resolution: {integrity: sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w==} + dev: false + + /trough@2.1.0: + resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} + dev: false + + /tsconfig-paths@3.14.1: + resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.7 + strip-bom: 3.0.0 + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib@2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + + /tsutils@3.21.0(typescript@5.1.3): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.1.3 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: false + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: false + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + + /typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /typescript@5.1.3: + resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + dev: true + + /underscore.string@3.3.6: + resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==} + dependencies: + sprintf-js: 1.1.2 + util-deprecate: 1.0.2 + dev: true + + /unified@10.1.1: + resolution: {integrity: sha512-v4ky1+6BN9X3pQrOdkFIPWAaeDsHPE1svRDxq7YpTc2plkIqFMwukfqM+l0ewpP9EfwARlt9pPFAeWYhHm8X9w==} + dependencies: + '@types/unist': 2.0.6 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.0.0 + trough: 2.0.2 + vfile: 5.3.0 + dev: false + + /unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + dependencies: + '@types/unist': 2.0.6 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.1.0 + vfile: 5.3.4 + dev: false + + /unique-stream@2.3.1: + resolution: {integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==} + dependencies: + json-stable-stringify-without-jsonify: 1.0.1 + through2-filter: 3.0.0 + dev: true + + /unist-builder@3.0.1: + resolution: {integrity: sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-find-after@4.0.0: + resolution: {integrity: sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.1.1 + dev: false + + /unist-util-generated@2.0.1: + resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + dev: false + + /unist-util-is@5.1.1: + resolution: {integrity: sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==} + dev: false + + /unist-util-is@5.2.0: + resolution: {integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==} + dev: false + + /unist-util-position@4.0.3: + resolution: {integrity: sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-remove-position@4.0.1: + resolution: {integrity: sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ==} + dependencies: + '@types/unist': 2.0.6 + unist-util-visit: 4.1.0 + dev: false + + /unist-util-stringify-position@3.0.0: + resolution: {integrity: sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-stringify-position@3.0.2: + resolution: {integrity: sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + dependencies: + '@types/unist': 2.0.6 + dev: false + + /unist-util-visit-parents@4.1.1: + resolution: {integrity: sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.1.1 + dev: false + + /unist-util-visit-parents@5.1.0: + resolution: {integrity: sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.1.1 + dev: false + + /unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.2.0 + dev: false + + /unist-util-visit@4.1.0: + resolution: {integrity: sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.1.1 + unist-util-visit-parents: 5.1.0 + dev: false + + /unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + dependencies: + '@types/unist': 2.0.6 + unist-util-is: 5.2.0 + unist-util-visit-parents: 5.1.3 + dev: false + + /universal-cookie@4.0.4: + resolution: {integrity: sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==} + dependencies: + '@types/cookie': 0.3.3 + cookie: 0.4.2 + dev: false + + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /update-browserslist-db@1.0.10(browserslist@4.21.4): + resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.4 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + dev: true + + /url-polyfill@1.1.12: + resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==} + dev: false + + /use-clipboard-copy@0.2.0(react@18.2.0): + resolution: {integrity: sha512-f0PMMwZ2/Hh9/54L12capx4s6ASdd6edNJxg2OcqWVNM8BPvtOSmNFIN1Dg/q//fPp8MpUZceHfr7cnWOS0RxA==} + peerDependencies: + react: '>=16.8.0' + dependencies: + clipboard-copy: 3.2.0 + react: 18.2.0 + dev: false + + /use-constant@1.1.1(react@18.2.0): + resolution: {integrity: sha512-sy2ttlE4kuAnNbp2P6a5aTZiGYwsZojkqaGZ31yDDjIurteUS8GOcYiPGmJ3y/LHOHkdazDdVRBZPzH3RZHffA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /uvu@0.5.3: + resolution: {integrity: sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==} + engines: {node: '>=8'} + hasBin: true + dependencies: + dequal: 2.0.2 + diff: 5.0.0 + kleur: 4.1.4 + sade: 1.8.1 + dev: false + + /uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + dequal: 2.0.3 + diff: 5.1.0 + kleur: 4.1.5 + sade: 1.8.1 + dev: false + + /value-or-function@3.0.0: + resolution: {integrity: sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==} + engines: {node: '>= 0.10'} + dev: true + + /vary@1.1.2: + resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} + engines: {node: '>= 0.8'} + dev: false + + /vfile-location@4.0.1: + resolution: {integrity: sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==} + dependencies: + '@types/unist': 2.0.6 + vfile: 5.3.4 + dev: false + + /vfile-message@3.1.0: + resolution: {integrity: sha512-4QJbBk+DkPEhBXq3f260xSaWtjE4gPKOfulzfMFF8ZNwaPZieWsg3iVlcmF04+eebzpcpeXOOFMfrYzJHVYg+g==} + dependencies: + '@types/unist': 2.0.6 + unist-util-stringify-position: 3.0.0 + dev: false + + /vfile-message@3.1.2: + resolution: {integrity: sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==} + dependencies: + '@types/unist': 2.0.6 + unist-util-stringify-position: 3.0.2 + dev: false + + /vfile-message@3.1.3: + resolution: {integrity: sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA==} + dependencies: + '@types/unist': 2.0.6 + unist-util-stringify-position: 3.0.3 + dev: false + + /vfile@5.3.0: + resolution: {integrity: sha512-Tj44nY/48OQvarrE4FAjUfrv7GZOYzPbl5OD65HxVKwLJKMPU7zmfV8cCgCnzKWnSfYG2f3pxu+ALqs7j22xQQ==} + dependencies: + '@types/unist': 2.0.6 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.0 + vfile-message: 3.1.0 + dev: false + + /vfile@5.3.4: + resolution: {integrity: sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==} + dependencies: + '@types/unist': 2.0.6 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.2 + vfile-message: 3.1.2 + dev: false + + /vfile@5.3.6: + resolution: {integrity: sha512-ADBsmerdGBs2WYckrLBEmuETSPyTD4TuLxTrw0DvjirxW1ra4ZwkbzG8ndsv3Q57smvHxo677MHaQrY9yxH8cA==} + dependencies: + '@types/unist': 2.0.6 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.3 + dev: false + + /vinyl-fs@3.0.3: + resolution: {integrity: sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==} + engines: {node: '>= 0.10'} + dependencies: + fs-mkdirp-stream: 1.0.0 + glob-stream: 6.1.0 + graceful-fs: 4.2.10 + is-valid-glob: 1.0.0 + lazystream: 1.0.1 + lead: 1.0.0 + object.assign: 4.1.4 + pumpify: 1.5.1 + readable-stream: 2.3.7 + remove-bom-buffer: 3.0.0 + remove-bom-stream: 1.2.0 + resolve-options: 1.1.0 + through2: 2.0.5 + to-through: 2.0.0 + value-or-function: 3.0.0 + vinyl: 2.2.1 + vinyl-sourcemap: 1.1.0 + dev: true + + /vinyl-sourcemap@1.1.0: + resolution: {integrity: sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==} + engines: {node: '>= 0.10'} + dependencies: + append-buffer: 1.0.2 + convert-source-map: 1.9.0 + graceful-fs: 4.2.10 + normalize-path: 2.1.1 + now-and-later: 2.0.1 + remove-bom-buffer: 3.0.0 + vinyl: 2.2.1 + dev: true + + /vinyl@2.2.1: + resolution: {integrity: sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==} + engines: {node: '>= 0.10'} + dependencies: + clone: 2.1.2 + clone-buffer: 1.0.0 + clone-stats: 1.0.0 + cloneable-readable: 1.1.3 + remove-trailing-separator: 1.1.0 + replace-ext: 1.0.1 + dev: true + + /vinyl@3.0.0: + resolution: {integrity: sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==} + engines: {node: '>=10.13.0'} + dependencies: + clone: 2.1.2 + clone-stats: 1.0.0 + remove-trailing-separator: 1.1.0 + replace-ext: 2.0.0 + teex: 1.0.1 + dev: true + + /void-elements@3.1.0: + resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=} + engines: {node: '>=0.10.0'} + dev: false + + /vue-template-compiler@2.7.14: + resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + dev: true + + /walk-sync@2.2.0: + resolution: {integrity: sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==} + engines: {node: 8.* || >= 10.*} + dependencies: + '@types/minimatch': 3.0.5 + ensure-posix-path: 1.1.1 + matcher-collection: 2.0.1 + minimatch: 3.1.2 + dev: true + + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + dev: false + + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + + /webworkify-webpack@2.1.5: + resolution: {integrity: sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==} + dev: false + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array@1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap@1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /zwitch@2.0.2: + resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==} + dev: false diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..f1c8dac8d2fd5682cf235f9987dfd91eff0aeb5e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + } +} diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..5b2bbcba7770b02aa9e61ccdda45f8018b8fbae2 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..1d841f0ea5e28023dbed2f1dcec5eb5bfb98d512 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60734010694121186ff018eab76c1269c783e441 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/demo.png b/public/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..c60f0bcb5d16c305ae0da807c2268e89f1b96554 Binary files /dev/null and b/public/demo.png differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..b9564f0434b10e4ec3561cc82dad578db7d5ffc7 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..edbb63e10699aae41a5132dbd7d9da4eaafb570b Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..73e6fb6a6fdfe77379b133f2b3cc17fbd0325d53 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/footer.png b/public/footer.png new file mode 100644 index 0000000000000000000000000000000000000000..245380a345ed7a818520c067b1d58e1251caccdb Binary files /dev/null and b/public/footer.png differ diff --git a/public/header.png b/public/header.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f2634e3021d51fd439a2c8db4ffd574889b9be Binary files /dev/null and b/public/header.png differ diff --git a/public/icons/128.png b/public/icons/128.png new file mode 100644 index 0000000000000000000000000000000000000000..5348e898ebfb447c793a174fcfac821c7a601726 Binary files /dev/null and b/public/icons/128.png differ diff --git a/public/icons/256.png b/public/icons/256.png new file mode 100644 index 0000000000000000000000000000000000000000..c592f4552f5e2ede30faffbfff85df079f73a2cc Binary files /dev/null and b/public/icons/256.png differ diff --git a/public/icons/512.png b/public/icons/512.png new file mode 100644 index 0000000000000000000000000000000000000000..00d0a21e69553a7049cb13657fd0bb7cbc4829df Binary files /dev/null and b/public/icons/512.png differ diff --git a/public/icons/64.png b/public/icons/64.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f86af2eae50d35927419bd4a1038a7f9b0e356 Binary files /dev/null and b/public/icons/64.png differ diff --git a/public/icons/ap.png b/public/icons/ap.png new file mode 100644 index 0000000000000000000000000000000000000000..8d2c03f08fc478b624cc8daf4ba40c911cff2ddb Binary files /dev/null and b/public/icons/ap.png differ diff --git a/public/icons/chicken.png b/public/icons/chicken.png new file mode 100644 index 0000000000000000000000000000000000000000..afe18eaba1b1e681cdfd55ed7710b83e065bf5df Binary files /dev/null and b/public/icons/chicken.png differ diff --git a/public/images/fabulous-celebration.png b/public/images/fabulous-celebration.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e0f39b1b63f2033518181f22cd847f5cf8a0fc Binary files /dev/null and b/public/images/fabulous-celebration.png differ diff --git a/public/images/fabulous-come-back-later.png b/public/images/fabulous-come-back-later.png new file mode 100644 index 0000000000000000000000000000000000000000..726bb9dec871863da4b2a142d2386abd594131db Binary files /dev/null and b/public/images/fabulous-come-back-later.png differ diff --git a/public/images/fabulous-fireworks.png b/public/images/fabulous-fireworks.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab46e45de34278c181a9094bba8a3dce3e6b96c Binary files /dev/null and b/public/images/fabulous-fireworks.png differ diff --git a/public/images/fabulous-page-not-found.png b/public/images/fabulous-page-not-found.png new file mode 100644 index 0000000000000000000000000000000000000000..6266a4950a5f69c7e060a804ddf763293ecd90ed Binary files /dev/null and b/public/images/fabulous-page-not-found.png differ diff --git a/public/images/fabulous-rip-2.png b/public/images/fabulous-rip-2.png new file mode 100644 index 0000000000000000000000000000000000000000..655fd0626cb0b1d0029c6cc50de0b1e01e8f6e74 Binary files /dev/null and b/public/images/fabulous-rip-2.png differ diff --git a/public/images/fabulous-wapmire-weekdays.png b/public/images/fabulous-wapmire-weekdays.png new file mode 100644 index 0000000000000000000000000000000000000000..b0d8c35789f8ebaba2881b1acda58f332ee2cdc2 Binary files /dev/null and b/public/images/fabulous-wapmire-weekdays.png differ diff --git a/public/images/step-2-screenshot.png b/public/images/step-2-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7037417069936c89586c9d6a885fc7fa4974cfb9 Binary files /dev/null and b/public/images/step-2-screenshot.png differ diff --git a/public/locales/de-DE/common.json b/public/locales/de-DE/common.json new file mode 100644 index 0000000000000000000000000000000000000000..76c0df027c217c91ad39d0afc71fcbfc77438dda --- /dev/null +++ b/public/locales/de-DE/common.json @@ -0,0 +1,122 @@ +{ + "- showing {{count}} page(s) ——one": "- zeigt {{count}} Seite", + "- showing {{count}} page(s) ——other": "- zeigt {{count}} Seiten", + "{{count}} item(s)——one": "{{count}} Element", + "{{count}} item(s)——other": "{{count}} Elemente", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> Wenn du nicht der Besitzer dieser Website bist, stoppe jetzt, da das Fortsetzen dieses Prozesses deine persönlichen Dateien in OneDrive offenlegen könnte.", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> Wenn du keine REDIS_URL in deiner Vercel env Variable angegeben hast, gehe zu <3>Upstash und initialisiere eine. Doku: <6>Vercel Integration - Upstash.", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> Wenn du etwas vermisst oder falsch siehst, musst du <3>/config/api.config.js neu konfigurieren und diese Instanz neu bereitstellen.", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ Du kannst nun zum nächsten Schritt übergehen: Fordere dein Access-Token und Refresh-Token an.", + "❌ No valid code extracted.": "❌ Kein gültiger Code extrahiert.", + "Acquired access_token: ": "Erworbenes access_token: ", + "Acquired refresh_token: ": "Erworbenes refresh_token: ", + "Actions": "Aktionen", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "Eine Autorisierung ist erforderlich, da kein gültiges <2>access_token oder <5>refresh_token auf dieser bereitgestellten Instanz vorhanden ist. Prüfe die folgenden Konfigurationen, bevor du mit der Autorisierung von onedrive-vercel-index mit deinem eigenen Microsoft-Konto fortfährst.", + "Cancel": "Abbrechen", + "Cannot preview {{path}}": "{{path}} kann nicht angezeigt werden", + "Change the raw file direct link to a URL ending with the extension of the file.": "Ändere den direkten Link zur rohen Datei in eine URL, die mit der Dateierweiterung endet.", + "Check out <2>Microsoft's official explanation on the error message.": "Sieh dir <2>Microsofts offizielle Erklärung zur Fehlermeldung an.", + "Clear all": "Alles entfernen", + "Clear all tokens?": "Alle Tokens löschen?", + "Cleared all tokens": "Alle Tokens gelöscht", + "clearing them means that you will need to re-enter the passwords again.": "Wenn du sie löschst, musst du die Passwörter erneut eingeben.", + "Copied direct link to clipboard.": "Direkten Link in die Zwischenablage kopiert.", + "Copied folder permalink.": "Permalink des Ordners kopiert.", + "Copied raw file permalink.": "Permalink der Rohdatei kopiert.", + "Copy direct link": "Direkten Link kopieren", + "Copy folder permalink": "Permalink des Ordners kopieren", + "Copy raw file permalink": "Permalink der Rohdatei kopieren", + "Copy the permalink to the file to the clipboard": "Kopiere den Permalink der Datei in die Zwischenablage", + "Customise direct link": "Direkten Link anpassen", + "Customise link": "Link anpassen", + "Customised": "Angepasst", + "Customised and encoded": "Angepasst und codiert", + "Copy selected files permalink": "Permalink der ausgewählten Dateien kopieren", + "Copied selected files permalink.": "Permalink der ausgewählten Dateien kopiert.", + "Default": "Standard", + "Do not pretend to be the site owner": "Gib dich nicht als der Seitenbesitzer aus", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "Keine Sorge, nachdem sie gespeichert sind, kümmert sich onedrive-vercel-index um Token-Aktualisierungen und Updates, nachdem deine Seite live geht.", + "Download": "Herunterladen", + "Download file": "Datei herunterladen", + "Download folder": "Ordner herunterladen", + "Download selected files": "Ausgewählte Dateien herunterladen", + "Download the file directly through OneDrive": "Die Datei direkt über OneDrive herunterladen", + "Downloading {{progress}}%": "Herunterladen {{progress}}%", + "Downloading folder, refresh page to cancel": "Ordner wird heruntergeladen, Seite aktualisieren zum Abbrechen", + "Downloading selected files, refresh page to cancel": "Ausgewählte Dateien werden heruntergeladen, Seite aktualisieren zum Abbrechen", + "Downloading selected files...": "Ausgewählte Dateien werden heruntergeladen...", + "Email": "E-Mail", + "Enter Password": "Passwort eingeben", + "Error storing the token": "Fehler beim Speichern des Tokens", + "Error validating identify, restart": "Fehler bei der Validierung der Identität, Neustart", + "Error: {{message}}": "Fehler: {{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "Fehler beim Herunterladen des Ordners {{path}}: {{status}} {{message}} Fortgesetzt, um fortzufahren.", + "Failed to download folder.": "Fehler beim Herunterladen des Ordners.", + "Failed to download selected files.": "Fehler beim Herunterladen der ausgewählten Dateien.", + "File is empty.": "Datei ist leer.", + "File size": "Dateigröße", + "Filename": "Dateiname", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "Letzter Schritt, klick auf den Button unten, um diese Tokens dauerhaft zu speichern, bevor sie nach {{minutes}} Minuten und {{seconds}} Sekunden ablaufen.", + "Finished downloading folder.": "Ordner-Download abgeschlossen.", + "Finished downloading selected files.": "Ausgewählte Dateien erfolgreich heruntergeladen.", + "Get tokens": "Tokens abrufen", + "Grid": "Gitter", + "Hashes": "Hashes", + "Home": "Start", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "Falls du zurück zum Start gehst und immer noch die Willkommensseite siehst, die dir sagt, du sollst dich neu authentifizieren, ", + "If you know the password, please enter it below.": "Wenn du das Passwort kennst, gib es bitte unten ein.", + "Last modified": "Zuletzt geändert", + "Last Modified": "Zuletzt geändert", + "Last modified:": "Zuletzt geändert:", + "List": "Liste", + "Load more": "Mehr laden", + "Loading ...": "Laden ...", + "Loading EPUB ...": "Lade EPUB ...", + "Loading file content...": "Dateiinhalt wird geladen...", + "Loading FLV extension...": "Lade FLV-Erweiterung...", + "Logout": "Ausloggen", + "MIME type": "MIME-Typ", + "Name": "Name", + "No more files": "Keine weiteren Dateien", + "Nothing here.": "Nichts zu sehen hier.", + "OAuth Step 1 - {{title}}": "OAuth Schritt 1 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth Schritt 2 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth Schritt 3 - {{title}}", + "of {{count}} file(s) -——loaded——one": "{{count}} Datei geladen -", + "of {{count}} file(s) -——loaded——other": "{{count}} Dateien geladen -", + "of {{count}} file(s) -——loading——one": "... Datei wird geladen -", + "of {{count}} file(s) -——loading——other": "... Dateien werden geladen -", + "Oops, that's a <1>four-oh-four.": "Ups, das ist ein <1>vier-null-vier.", + "Open URL": "URL öffnen", + "Open URL{{url}}": "URL öffnen{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "Drücke <2>F12 und öffne die Entwicklertools für mehr Details, oder such Hilfe in den <6>onedrive-vercel-index Diskussionen.", + "Proceed to OAuth": "Weiter zu OAuth", + "Requesting tokens": "Tokens anfordern", + "Restart": "Neustart", + "revisit home and do a hard refresh.": "geh zurück zur Startseite und mach einen harten Refresh.", + "Search ...": "Suche ...", + "Select all files": "Alle Dateien auswählen", + "Select file": "Eine Datei auswählen", + "Select files": "Dateien auswählen", + "Size": "Größe", + "Step 1/3: Preparations": "Schritt 1/3: Vorbereitungen", + "Step 2/3: Get authorisation code": "Schritt 2/3: Autorisierungscode erhalten", + "Step 3/3: Get access and refresh tokens": "Schritt 3/3: Zugriffs- und Aktualisierungstokens erhalten", + "Store tokens": "Tokens speichern", + "Stored! Going home...": "Gespeichert! Zurück zur Startseite...", + "Storing tokens": "Tokens speichern", + "Success! The API returned what we needed.": "Erfolg! Die API hat geliefert, was wir brauchten.", + "The authorisation code extracted is:": "Der extrahierte Autorisierungscode ist:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "Der OAuth-Link zum Erhalten des Autorisierungscodes wurde erstellt. Klicke auf den Link oben, um den <2>Autorisierungscode zu bekommen. Dein Browser wird einen neuen Tab zur Microsoft-Account-Anmeldeseite öffnen. Nach dem Einloggen und Authentifizieren mit deinem Microsoft-Konto wirst du auf eine leere Seite auf localhost weitergeleitet. Füge die <6>gesamte umgeleitete URL unten ein.", + "These tokens are used to authenticate yourself into password protected folders, ": "Diese Tokens werden verwendet, um dich in passwortgeschützte Ordner zu authentifizieren, ", + "These tokens may take a few seconds to populate after you click the button below. ": "Es kann ein paar Sekunden dauern, bis diese Tokens nach dem Klicken auf den Button unten angezeigt werden. ", + "This route (the folder itself and the files inside) is password protected. ": "Dieser Pfad (der Ordner selbst und die Dateien darin) ist passwortgeschützt. ", + "Unavailable": "Nicht verfügbar", + "URL encoded": "URL kodiert", + "Waiting for code...": "Warte auf Code...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "Willkommen bei deinem neuen Onedrive-vercel-index 🎉", + "What is this?": "Was ist das?", + "Where is the auth code? Did you follow step 2 you silly donut?": "Wo ist der Auth-Code? Hast du Schritt 2 befolgt, du alberner Donut?", + "Whoops, looks like we got a problem: {{error}}.": "Ups, sieht aus als hätten wir ein Problem: {{error}}." +} \ No newline at end of file diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 0000000000000000000000000000000000000000..739bdb457bfdaf9ece34b99b4500eb0aab453822 --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,122 @@ +{ + "- showing {{count}} page(s) ——one": "- showing {{count}} page ", + "- showing {{count}} page(s) ——other": "- showing {{count}} pages ", + "{{count}} item(s)——one": "{{count}} item", + "{{count}} item(s)——other": "{{count}} items", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ You can now proceed onto the next step: requesting your access token and refresh token.", + "❌ No valid code extracted.": "❌ No valid code extracted.", + "Acquired access_token: ": "Acquired access_token: ", + "Acquired refresh_token: ": "Acquired refresh_token: ", + "Actions": "Actions", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.", + "Cancel": "Cancel", + "Cannot preview {{path}}": "Cannot preview {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": "Change the raw file direct link to a URL ending with the extension of the file.", + "Check out <2>Microsoft's official explanation on the error message.": "Check out <2>Microsoft's official explanation on the error message.", + "Clear all": "Clear all", + "Clear all tokens?": "Clear all tokens?", + "Cleared all tokens": "Cleared all tokens", + "clearing them means that you will need to re-enter the passwords again.": "clearing them means that you will need to re-enter the passwords again.", + "Copied direct link to clipboard.": "Copied direct link to clipboard.", + "Copied folder permalink.": "Copied folder permalink.", + "Copied raw file permalink.": "Copied raw file permalink.", + "Copy direct link": "Copy direct link", + "Copy folder permalink": "Copy folder permalink", + "Copy raw file permalink": "Copy raw file permalink", + "Copy the permalink to the file to the clipboard": "Copy the permalink to the file to the clipboard", + "Customise direct link": "Customise direct link", + "Customise link": "Customise link", + "Customised": "Customised", + "Customised and encoded": "Customised and encoded", + "Copy selected files permalink": "Copy selected files permalink", + "Copied selected files permalink.": "Copied selected files permalink.", + "Default": "Default", + "Do not pretend to be the site owner": "Do not pretend to be the site owner", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.", + "Download": "Download", + "Download file": "Download file", + "Download folder": "Download folder", + "Download selected files": "Download selected files", + "Download the file directly through OneDrive": "Download the file directly through OneDrive", + "Downloading {{progress}}%": "Downloading {{progress}}%", + "Downloading folder, refresh page to cancel": "Downloading folder, refresh page to cancel", + "Downloading selected files, refresh page to cancel": "Downloading selected files, refresh page to cancel", + "Downloading selected files...": "Downloading selected files...", + "Email": "Email", + "Enter Password": "Enter Password", + "Error storing the token": "Error storing the token", + "Error validating identify, restart": "Error validating identify, restart", + "Error: {{message}}": "Error: {{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.", + "Failed to download folder.": "Failed to download folder.", + "Failed to download selected files.": "Failed to download selected files.", + "File is empty.": "File is empty.", + "File size": "File size", + "Filename": "Filename", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ", + "Finished downloading folder.": "Finished downloading folder.", + "Finished downloading selected files.": "Finished downloading selected files.", + "Get tokens": "Get tokens", + "Grid": "Grid", + "Hashes": "Hashes", + "Home": "Home", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "If you go back home and still see the welcome page telling you to re-authenticate, ", + "If you know the password, please enter it below.": "If you know the password, please enter it below.", + "Last modified": "Last modified", + "Last Modified": "Last Modified", + "Last modified:": "Last modified:", + "List": "List", + "Load more": "Load more", + "Loading ...": "Loading ...", + "Loading EPUB ...": "Loading EPUB ...", + "Loading file content...": "Loading file content...", + "Loading FLV extension...": "Loading FLV extension...", + "Logout": "Logout", + "MIME type": "MIME type", + "Name": "Name", + "No more files": "No more files", + "Nothing here.": "Nothing here.", + "OAuth Step 1 - {{title}}": "OAuth Step 1 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth Step 2 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth Step 3 - {{title}}", + "of {{count}} file(s) -——loaded——one": "of {{count}} file -", + "of {{count}} file(s) -——loaded——other": "of {{count}} files -", + "of {{count}} file(s) -——loading——one": "of ... file(s) -", + "of {{count}} file(s) -——loading——other": "of ... file(s) -", + "Oops, that's a <1>four-oh-four.": "Oops, that's a <1>four-oh-four.", + "Open URL": "Open URL", + "Open URL{{url}}": "Open URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.", + "Proceed to OAuth": "Proceed to OAuth", + "Requesting tokens": "Requesting tokens", + "Restart": "Restart", + "revisit home and do a hard refresh.": "revisit home and do a hard refresh.", + "Search ...": "Search ...", + "Select all files": "Select all files", + "Select file": "Select file", + "Select files": "Select files", + "Size": "Size", + "Step 1/3: Preparations": "Step 1/3: Preparations", + "Step 2/3: Get authorisation code": "Step 2/3: Get authorisation code", + "Step 3/3: Get access and refresh tokens": "Step 3/3: Get access and refresh tokens", + "Store tokens": "Store tokens", + "Stored! Going home...": "Stored! Going home...", + "Storing tokens": "Storing tokens", + "Success! The API returned what we needed.": "Success! The API returned what we needed.", + "The authorisation code extracted is:": "The authorisation code extracted is:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.", + "These tokens are used to authenticate yourself into password protected folders, ": "These tokens are used to authenticate yourself into password protected folders, ", + "These tokens may take a few seconds to populate after you click the button below. ": "These tokens may take a few seconds to populate after you click the button below. ", + "This route (the folder itself and the files inside) is password protected. ": "This route (the folder itself and the files inside) is password protected. ", + "Unavailable": "Unavailable", + "URL encoded": "URL encoded", + "Waiting for code...": "Waiting for code...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "Welcome to your new onedrive-vercel-index 🎉", + "What is this?": "What is this?", + "Where is the auth code? Did you follow step 2 you silly donut?": "Where is the auth code? Did you follow step 2 you silly donut?", + "Whoops, looks like we got a problem: {{error}}.": "Whoops, looks like we got a problem: {{error}}." +} diff --git a/public/locales/es/common.json b/public/locales/es/common.json new file mode 100644 index 0000000000000000000000000000000000000000..1e6fb4e237eee2bf07d790de3ad5c5b7a485dc7c --- /dev/null +++ b/public/locales/es/common.json @@ -0,0 +1,122 @@ +{ + "- showing {{count}} page(s) ——one": "- mostrando {{count}} página ", + "- showing {{count}} page(s) ——other": "- mostrando{{count}} páginas ", + "{{count}} item(s)——one": "{{count}} elemento", + "{{count}} item(s)——other": "{{count}} elementos", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> Si no eres el propietario de este sitio web, detente ahora, ya que continuar con este proceso puede exponer tus archivos personales en OneDrive.", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> Si no has especificado una URL REDIS dentro de tu variable Vercel env, ve a inicializar una en <3>Upstash. Documentación: <6>Integración Vercel - Upstash.", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> Si ves algo que falte o esté incorrecto, debes reconfigurar <3>/config/api.config.js y volver a lanzar esta instancia.", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ Ahora puedes avanzar al siguiente paso: solicitar tu token de acceso y token de actualización.", + "❌ No valid code extracted.": "❌ No se ha extraído ningún código válido.", + "Acquired access_token: ": "access_token adquirido: ", + "Acquired refresh_token: ": "refresh_token adquirido: ", + "Actions": "Acciones", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "Autorización requerida ya que no hay un <2>token de acceso válido o un <5>token de actualización presente en esta instancia desplegada. Verifica las siguientes configuraciones antes de continuar autorizando onedrive-vercel-index con tu propia cuenta de Microsoft.", + "Cancel": "Cancelar", + "Cannot preview {{path}}": "No se puede previsualizar {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": "Cambia el enlace directo del archivo sin formato a una URL que termine con la extensión del archivo.", + "Check out <2>Microsoft's official explanation on the error message.": "Consulta la <2>explicación oficial de Microsoft sobre el mensaje de error.", + "Clear all": "Limpiar todo", + "Clear all tokens?": "¿Borrar todos los tokens?", + "Cleared all tokens": "Se han borrado todos los tokens", + "clearing them means that you will need to re-enter the passwords again.": "borrarlos significa que tendrás que volver a ingresar las contraseñas de nuevo.", + "Copied direct link to clipboard.": "Enlace directo copiado al portapapeles.", + "Copied folder permalink.": "Permalink de la carpeta copiado.", + "Copied raw file permalink.": "Permalink del archivo sin formato copiado.", + "Copy direct link": "Copiar enlace directo", + "Copy folder permalink": "Copiar permalink de la carpeta", + "Copy raw file permalink": "Copiar permalink del archivo sin formato", + "Copy the permalink to the file to the clipboard": "Copiar el permalink del archivo al portapapeles", + "Customise direct link": "Personalizar enlace directo", + "Customise link": "Personalizar enlace", + "Customised": "Personalizado", + "Customised and encoded": "Personalizado y codificado", + "Copy selected files permalink": "Copiar permalink de archivos seleccionados", + "Copied selected files permalink.": "Permalink de archivos seleccionados copiado.", + "Default": "Por defecto", + "Do not pretend to be the site owner": "No fingir ser el propietario del sitio", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "No te preocupes, después de almacenarlos, onedrive-vercel-index se encargará de actualizar y refrescar los tokens después de que tu sitio sea publicado.", + "Download": "Descargar", + "Download file": "Descargar archivo", + "Download folder": "Descargar carpeta", + "Download selected files": "Descargar archivos seleccionados", + "Download the file directly through OneDrive": "Descargar el archivo directamente a través de OneDrive", + "Downloading {{progress}}%": "Descargando {{progress}}%", + "Downloading folder, refresh page to cancel": "Descargando carpeta, actualice la página para cancelar", + "Downloading selected files, refresh page to cancel": "Descargando archivos seleccionados, actualice la página para cancelar", + "Downloading selected files...": "Descargando archivos seleccionados...", + "Email": "Correo electrónico", + "Enter Password": "Ingrese la contraseña", + "Error storing the token": "Error al almacenar el token", + "Error validating identify, restart": "Error al validar la identidad, reinicie", + "Error: {{message}}": "Error: {{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "Error al descargar la carpeta {{path}}: {{status}} {{message}} Se saltó para continuar.", + "Failed to download folder.": "Error al descargar la carpeta.", + "Failed to download selected files.": "Error al descargar los archivos seleccionados.", + "File is empty.": "El archivo está vacío.", + "File size": "Tamaño del archivo", + "Filename": "Nombre de archivo", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "Paso final, haz clic en el botón a continuación para almacenar estos tokens de manera persistente antes de que expire después de {{minutes}} minutos {{seconds}} segundos. ", + "Finished downloading folder.": "Finalizó la descarga de la carpeta.", + "Finished downloading selected files.": "Archivos seleccionados descargados completados.", + "Get tokens": "Obtener tokens", + "Grid": "Cuadrícula", + "Hashes": "Hashes", + "Home": "Inicio", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "Si vuelves a la página de inicio y aún ves la página de bienvenida que te dice que vuelvas a autenticarte, ", + "If you know the password, please enter it below.": "Si conoces la contraseña, ingrésala debajo.", + "Last modified": "Última modificación", + "Last Modified": "Última modificación", + "Last modified:": "Última modificación:", + "List": "Lista", + "Load more": "Cargar más", + "Loading ...": "Cargando ...", + "Loading EPUB ...": "Cargando EPUB ...", + "Loading file content...": "Cargando contenido del archivo...", + "Loading FLV extension...": "Cargando extensión FLV...", + "Logout": "Cerrar sesión", + "MIME type": "Tipo MIME", + "Name": "Nombre", + "No more files": "No hay más archivos", + "Nothing here.": "Nada aquí.", + "OAuth Step 1 - {{title}}": "OAuth Paso 1 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth Paso 2 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth Paso 3 - {{title}}", + "of {{count}} file(s) -——loaded——one": "de {{count}} archivo -", + "of {{count}} file(s) -——loaded——other": "de {{count}} archivos -", + "of {{count}} file(s) -——loading——one": "de ... archivo(s) -", + "of {{count}} file(s) -——loading——other": "de ... archivo(s) -", + "Oops, that's a <1>four-oh-four.": "Vaya, es un <1>cuatro-cero-cuatro.", + "Open URL": "Abrir URL", + "Open URL{{url}}": "Abrir URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "Presiona <2>F12 y abre devtools para más detalles o busca ayuda en <6>discusiones onedrive-vercel-index.", + "Proceed to OAuth": "Proceder a OAuth", + "Requesting tokens": "Solicitando tokens", + "Restart": "Reiniciar", + "revisit home and do a hard refresh.": "vuelve a la página principal y haz una recarga completa.", + "Search ...": "Buscar ...", + "Select all files": "Seleccionar todos los archivos", + "Select file": "Seleccionar archivo", + "Select files": "Seleccionar archivos", + "Size": "Tamaño", + "Step 1/3: Preparations": "Paso 1/3: Preparativos", + "Step 2/3: Get authorisation code": "Obtener código de autorización - Paso 2/3", + "Step 3/3: Get access and refresh tokens": "Obtener tokens de acceso y actualización - Paso 3/3", + "Store tokens": "Almacenar tokens", + "Stored! Going home...": "¡Almacenados! Regresando a Inicio...", + "Storing tokens": "Almacenando tokens", + "Success! The API returned what we needed.": "¡Éxito! La API devolvió lo que necesitábamos.", + "The authorisation code extracted is:": "El código de autorización extraído es:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "El enlace OAuth para obtener el código de autorización se ha creado. Haz clic en el enlace anterior para obtener el <2>código de autorización. Tu navegador abrirá una nueva pestaña en la página de inicio de sesión de la cuenta de Microsoft. Después de iniciar sesión y autenticarse con tu cuenta de Microsoft, serás redirigido a una página en blanco en localhost. Pega <6>toda la URL redirigida a continuación.", + "These tokens are used to authenticate yourself into password protected folders, ": "Estos tokens se utilizan para autenticarse en carpetas protegidas con contraseña,", + "These tokens may take a few seconds to populate after you click the button below. ": "Estos tokens pueden demorar unos segundos en ser llenados después de hacer clic en el botón de abajo. ", + "This route (the folder itself and the files inside) is password protected. ": "Esta ruta (la carpeta y los archivos dentro de ella) está protegida por contraseña.", + "Unavailable": "No disponible", + "URL encoded": "Codificado en URL", + "Waiting for code...": "Esperando código...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "Bienvenido a tu nuevo onedrive-vercel-index 🎉", + "What is this?": "¿Qué es esto?", + "Where is the auth code? Did you follow step 2 you silly donut?": "¿Dónde está el código de autorización? ¿Siguiste el paso 2, bobo?", + "Whoops, looks like we got a problem: {{error}}.": "¡Vaya, parece que tenemos un problema: {{error}}." +} \ No newline at end of file diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json new file mode 100644 index 0000000000000000000000000000000000000000..2e8036265303c11de2cc55d916437d35e46b9908 --- /dev/null +++ b/public/locales/hi/common.json @@ -0,0 +1,116 @@ +{ + "- showing {{count}} page(s) ——other": "पेज {{count}} दिखा रहा है|", + "{{count}} item(s)——other": "{{count}} दिखा रहा है|", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> यदि आप इस वेबसाइट के स्वामी नहीं हैं, तो अभी रुकें, क्योंकि इस प्रक्रिया को जारी रखने से आपकी व्यक्तिगत फ़ाइलें OneDrive में दिखाई दे सकती हैं।", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "यदि आपने अपने Vercel env वेरिएबल के अंदर REDIS_URL निर्दिष्ट नहीं किया है, तो एक को इनिशियलाइज़ करें<3>Upstash दस्तावेज़ीकरण。文档:<6>Vercel 集成 - Upstash。", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> यदि आपको कुछ गुम या गलत दिखाई देता है, तो आपको पुन: कॉन्फ़िगर करने की आवश्यकता है| <3>/config/api.config.js पुन: नियोजित करने का प्रयास करें|", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ अब आप अगले चरण पर आगे बढ़ सकते हैं: अपने एक्सेस टोकन का अनुरोध करना और टोकन रीफ्रेश करना ", + "❌ No valid code extracted.": "❌ कोई मान्य कोड नहीं निकाला गया ", + "Acquired access_token: ": "अधिग्रहीत access_token", + "Acquired refresh_token: ": "अधिग्रहीत refresh_token", + "Actions": "कार्रवाई", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "प्राधिकरण की आवश्यकता है क्योंकि कोई मान्य नहीं है <2>access_token या <5>refresh_token,इस तैनात उदाहरण पर मौजूद है। अधिकृत करने के साथ आगे बढ़ने से पहले निम्नलिखित विन्यासों की जाँच करें onedrive-vercel-index अपना Microsoft account。", + "Cancel": "रद्द करें", + "Cannot preview {{path}}": "नहीं दिखा सकता {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": "फ़ाइल एक्सटेंशन के साथ समाप्त करने के लिए फ़ाइल का सीधा लिंक बदलें URL。", + "Check out <2>Microsoft's official explanation on the error message.": "कृपया जांचें <2>Microsoft आधिकारिक स्पष्टीकरण विस्तृत त्रुटि जानकारी के लिए。", + "Clear all": "सभी साफ करें", + "Clear all tokens?": "सभी टोकन साफ़ करें?", + "Cleared all tokens": "सभी टोकन साफ़ कर दिए", + "clearing them means that you will need to re-enter the passwords again.": "उन्हें साफ़ करने का अर्थ है कि आपको पासवर्ड फिर से दर्ज करने होंगे。", + "Copied direct link to clipboard.": "क्लिपबोर्ड पर सीधा लिंक कॉपी किया गया。", + "Copied folder permalink.": "कॉपी किया हुआ फोल्डर परमालिंक。", + "Copied raw file permalink.": "कॉपी की गई कच्ची फ़ाइल परमालिंक。", + "Copy direct link": "सीधा लिंक कॉपी करें", + "Copy folder permalink": "कॉपी फोल्डर परमालिंक", + "Copy raw file permalink": "कच्ची फ़ाइल को कॉपी करें स्थायी लिंक", + "Copy the permalink to the file to the clipboard": "फ़ाइल के परमालिंक को क्लिपबोर्ड पर कॉपी करें", + "Customise direct link": "सीधा लिंक अनुकूलित करें", + "Customise link": "लिंक अनुकूलित करें", + "Customised": "स्वनिर्धारित", + "Customised and encoded": "अनुकूलित और एन्कोडेड", + "Default": "चूक जाना", + "Do not pretend to be the site owner": "साइट के स्वामी होने का दिखावा न करें", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "चिंता न करें, उन्हें स्टोर करने के बाद,onedrive-vercel-index आपकी साइट के लाइव होने के बाद टोकन रीफ्रेश और अपडेट का ध्यान रखेगा।", + "Download": "डाउनलोड", + "Download file": "फ़ाइल डाउनलोड करें", + "Download folder": "डाउनलोड फ़ोल्डर", + "Download selected files": "चयनित फ़ाइलें डाउनलोड करें", + "Download the file directly through OneDrive": "फ़ाइल को सीधे OneDrive के माध्यम से डाउनलोड करें", + "Downloading {{progress}}%": "डाउनलोड {{progress}}%", + "Downloading folder, refresh page to cancel": "फोल्डर डाउनलोड हो रहा है, रद्द करने के लिए पेज को रिफ्रेश करें", + "Downloading selected files, refresh page to cancel": "चयनित फ़ाइलें डाउनलोड कर रहा है, रद्द करने के लिए पृष्ठ को ताज़ा करें", + "Downloading selected files...": "चयनित फ़ाइलें डाउनलोड हो रही हैं...", + "Email": "ईमेल", + "Enter Password": "पास वर्ड दर्ज करें", + "Error storing the token": "टोकन संग्रहीत करने में त्रुटि", + "Error validating identify, restart": "पहचान सत्यापित करने में त्रुटि, पुनरारंभ करें", + "Error: {{message}}": "त्रुटि:{{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "फ़ोल्डर डाउनलोड करने में विफल {{path}} विफल:{{status}} {{message}} जारी रखने के लिए इसे छोड़ दिया।", + "Failed to download folder.": "फ़ोल्डर डाउनलोड करने में विफल", + "Failed to download selected files.": "चयनित फ़ाइल डाउनलोड करने में विफल", + "File is empty.": "फ़ाइल खाली है", + "File size": "फाइल का आकार", + "Filename": "फ़ाइल का नाम", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "अंतिम चरण, इन टोकन के समाप्त होने से पहले लगातार स्टोर करने के लिए नीचे दिए गए बटन पर क्लिक करें {{minutes}} मिनट {{seconds}} सेकंड.", + "Finished downloading folder.": "फ़ोल्डर डाउनलोड करना समाप्त हुआ", + "Finished downloading selected files.": "चयनित फ़ाइलों को डाउनलोड करना समाप्त।", + "Get tokens": "प्राप्त tokens", + "Grid": "ग्रिड", + "Hashes": "हैश", + "Home": "घर", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "यदि आप घर वापस जाते हैं और फिर भी स्वागत पृष्ठ देखते हैं जो आपको पुनः प्रमाणित करने के लिए कह रहा है,", + "If you know the password, please enter it below.": "यदि आप पासवर्ड जानते हैं, तो कृपया इसे नीचे दर्ज करें।", + "Last modified": "अंतिम बार संशोधित", + "Last Modified": "अंतिम बार संशोधित", + "Last modified:": "अंतिम बार संशोधित:", + "List": "सूची", + "Load more": "और लोड करें", + "Loading ...": "लोड हो रहा है ...", + "Loading EPUB ...": "EPUB लोड हो रहा है...", + "Loading file content...": "फ़ाइल सामग्री लोड हो रही है...", + "Loading FLV extension...": "FLV एक्सटेंशन लोड हो रहा है...", + "Logout": "लॉग आउट", + "MIME type": "MIME प्रकार", + "Name": "नाम", + "No more files": "कोई और फाइल नहीं", + "Nothing here.": "यहाँ कुछ नहीं।", + "OAuth Step 1 - {{title}}": "OAuth कदम 1 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth कदम 2 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth कदम 3 - {{title}}", + "of {{count}} file(s) -——loaded——other": "का {{count}} फ़ाइलें", + "of {{count}} file(s) -——loading——other": "का…फ़ाइलें", + "Oops, that's a <1>four-oh-four.": "उफ़,यहाँ है <1>404 पृष्ठ", + "Open URL": "खोलो URL", + "Open URL{{url}}": "खोलो URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "कृपया दबाएं <2>F12 विवरण के लिए डेवलपर टूल विंडो खोलने के लिए, या यहां जाएं <6>onedrive-vercel-index सामुदायिक चर्चा सहायता के लिए।", + "Proceed to OAuth": "OAuth के लिए आगे बढ़ें", + "Requesting tokens": "टोकन का अनुरोध", + "Restart": "पुनर्प्रारंभ करें", + "revisit home and do a hard refresh.": "घर फिर से आएं और एक कठिन रिफ्रेश करें।", + "Search ...": "खोज ...", + "Select all files": "सभी फाइलों का चयन करें", + "Select file": "इस फ़ाइल का चयन करें", + "Select files": "निम्नलिखित फाइलों का चयन करें", + "Size": "फाइल का आकार", + "Step 1/3: Preparations": "कदम 1/3:तैयार करना", + "Step 2/3: Get authorisation code": "कदम 2/3:प्राधिकरण कोड प्राप्त करें", + "Step 3/3: Get access and refresh tokens": "कदम 3/3:पहुंच प्राप्त करें और टोकन रीफ्रेश करें", + "Store tokens": "टोकन स्टोर करें", + "Stored! Going home...": "संग्रहित! घर जा रहा है...", + "Storing tokens": "टोकन जमा कर रहे हैं...", + "Success! The API returned what we needed.": "सफलता! एपीआई ने वही लौटाया जो हमें चाहिए था।", + "The authorisation code extracted is:": "निकाला गया प्राधिकरण कोड है:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "बनाए गए OAuth लिंक का उपयोग प्राधिकरण कोड प्राप्त करने के लिए किया जाता है। आवश्यक <2>प्राधिकरण कोड प्राप्त करने के लिए ऊपर दिए गए लिंक पर क्लिक करें। आपका ब्राउज़र एक नए टैब में Microsoft खाता लॉगिन पृष्ठ खोलेगा। लॉग इन करने और अपने Microsoft खाते को सत्यापित करने के बाद, आपको डोमेन नाम localhost के साथ एक रिक्त पृष्ठ पर पुनर्निर्देशित किया जाएगा। कृपया नीचे <6>पूर्ण पुनर्निर्देशित URL को कॉपी और पेस्ट करें।", + "These tokens are used to authenticate yourself into password protected folders, ": "इन टोकन का उपयोग पासवर्ड से सुरक्षित फ़ोल्डरों में खुद को प्रमाणित करने के लिए किया जाता है, ", + "These tokens may take a few seconds to populate after you click the button below. ": "आपके द्वारा नीचे दिए गए बटन पर क्लिक करने के बाद इन टोकन को पॉप्युलेट होने में कुछ सेकंड लग सकते हैं।", + "This route (the folder itself and the files inside) is password protected. ": "यह मार्ग (स्वयं फ़ोल्डर और अंदर की फाइलें) पासवर्ड से सुरक्षित है। ", + "Unavailable": "अनुपलब्ध", + "URL encoded": "URL एन्कोडेड", + "Waiting for code...": "कोड की प्रतीक्षा की जा रही है...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "आपके नए onedrive-vercel-index में आपका स्वागत है 🎉", + "What is this?": "यह क्या है?", + "Where is the auth code? Did you follow step 2 you silly donut?": "ऑथ कोड कहां है? क्या आपने चरण 2 का पालन किया, मूर्ख डोनट?", + "Whoops, looks like we got a problem: {{error}}.": "ओह, ऐसा लगता है कि हमें कोई समस्या हो गई है:{{error}}" +} diff --git a/public/locales/id/common.json b/public/locales/id/common.json new file mode 100644 index 0000000000000000000000000000000000000000..10b47a4fe6fed954e11079ff0faa17f9ee14de8e --- /dev/null +++ b/public/locales/id/common.json @@ -0,0 +1,123 @@ +{ + "- showing {{count}} page(s) ——one": "- menampilkan {{count}} halaman ", + "- showing {{count}} page(s) ——other": "- menampilkan {{count}} halaman ", + "{{count}} item(s)——one": "{{count}} barang", + "{{count}} item(s)——other": "{{count}} barang", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> Jika anda bukan pemilik situs ini, berhentilah sekarang, karena melanjutkan ini mungkin bisa membongkar berkas pribadi anda di OneDrive.", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0>Jika Anda tidak menentukan REDIS_URL inside di your Vercel env variable, silahkan inisialisasi satu di <3>Upstash. Dokumentasi: <6>Vercel Integration - Upstash.", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> Jika anda melihat sesuatu yang kosong atau salah, anda harus mengkonfigurasi ulang <3>/config/api.config.js dan deploy ulang instance.", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ Anda sekarang bisa melanjutkan ke langkah berikutnya: meminta token akses anda dan refresh token.", + "❌ No valid code extracted.": "❌ Tidak ada kode valid yang diekstrak.", + "Acquired access_token: ": "Mendapatkan access_token: ", + "Acquired refresh_token: ": "Mendapatkan refresh_token: ", + "Actions": "Aksi", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "Izin diperlukan karena tidak ada <2>access_token atau <5>refresh token yang valid pada instance yang di-deploy ini. Periksalah konfigurasi berikut sebelum melanjutkan dengan mengotorisasi onedrive-vercel-index dengan akun Microsoft Anda sendiri.", + "Cancel": "Batal", + "Cannot preview {{path}}": "Tidak bisa melihat {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": " Ganti link langsung berkas raw ke URL yang berakhir dengan ekstensi berkas.", + "Check out <2>Microsoft's official explanation on the error message.": "Lihat <2>penjelasan resi Microsoft pada pesan Kesalahan tersebut.", + "Clear all": "Bersihkan semua", + "Clear all tokens?": "Bersihkan semua token?", + "Cleared all tokens": "Membersihkan semua token", + "clearing them means that you will need to re-enter the passwords again.": "menghapus mereka berarti Anda harus memasukkan ulang sandi lagi.", + "Copied direct link to clipboard.": "Tautan langsung telah disalin ke clipboard.", + "Copied folder permalink.": "Tautan folder telah disalin.", + "Copied raw file permalink.": "Tautan berkas mentah telah disalin.", + "Copy direct link": "Salin tautan langsung", + "Copy folder permalink": "Salin tautan folder", + "Copy raw file permalink": "Salin tautan berkas mentah", + "Copy the permalink to the file to the clipboard": "Salin tautan ke berkas ke clipboard", + "Customise direct link": "Kustomisasi tautan langsung", + "Customise link": "Kustomisasi tautan", + "Customised": "Dikustomisasi", + "Customised and encoded": "Dikustomisasi dan dienkode", + "Copy selected files permalink": "Salin tautan berkas terpilih", + "Copied selected files permalink.": "Tautan berkas terpilih telah disalin.", + "Default": "Bawaan", + "Do not pretend to be the site owner": "Jangan berpura pura menjadi pemilik situs", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "Jangan khawatir, onedrive-vercel-index akan mengurus token refresh dan memperbarui setelah situs anda live.", + "Download": "Unduh", + "Download file": "Unduh berkas", + "Download folder": "Unduh folder", + "Download selected files": "Unduh berkas yang dipilih", + "Download the file directly through OneDrive": "Unduh berkasnya langsung dari OneDrive", + "Downloading {{progress}}%": "Mengunduh {{progress}}%", + "Downloading folder, refresh page to cancel": "Mengunduh folder, muat ulang halaman untuk membatalkan", + "Downloading selected files, refresh page to cancel": "Mengunduh berkas yang dipilih, muat ulang halaman untuk membatalkan", + "Downloading selected files...": "Mengunduh berkas yang dipilih...", + "Email": "Email", + "Enter Password": "Masukkan sandi", + "Error storing the token": "Kesalahan saat menyimpan token", + "Error validating identify, restart": "Kesalahan saat memvalidasi identitas, restart", + "Error: {{message}}": "Kesalahan: {{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "Gagal mengunduh folder {{path}}: {{status}} {{message}} Melewati untuk melanjutkan.", + "Failed to download folder.": "Gagal untuk mengunduh folder.", + "Failed to download selected files.": "Gagal mengunduh berkas yang dipilih.", + "File is empty.": "Berkas kosong.", + "File size": "Ukuran berkas", + "Filename": "Nama berkas", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "Langkah terakhir, klik pada tombol bawah untuk menyimpan token secara konsisten sebelum kadaluarsa pada {{minutes}} menit {{seconds}} detik.", + "Finished downloading folder.": "Selesai mendownload folder.", + "Finished downloading selected files.": "Selesai mengunduh berkas yang dipilih.", + "Get tokens": "Dapatkan token", + "Grid": "Kisi", + "Hashes": "Hashes", + "Home": "Halaman Utama", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "Jika Anda kembali ke halaman utama dan masih melihat halaman Selamat Datang mengatakan untuk mengotorisasi ulang, ", + "If you know the password, please enter it below.": "Jika anda tau passwordnya, silahkan masukkan dibawah.", + "Last modified": "Terakhir dimodifikasi", + "Last Modified": "Terakhir dimodifikasi", + "Last modified:": "Terakhir dimodifikasi:", + "List": "Daftar", + "Load more": "Muat lebih banyak", + "Loading ...": "Memuat...", + "Loading EPUB ...": "Memuat EPUB...", + "Loading file content...": "Memuat konten berkas...", + "Loading FLV extension...": "Memuat ekstensi FLV...", + "Logout": "Keluar", + "MIME type": "Tipe MIME", + "Name": "Nama", + "No more files": "Tidak ada berkas lagi", + "Nothing here.": "Tidak ada apa apa disini.", + "OAuth Step 1 - {{title}}": "Langkah 1 OAuth - {{title}}", + "OAuth Step 2 - {{title}}": "Langkah 2 OAuth - {{title}}", + "OAuth Step 3 - {{title}}": "Langkah 3 OAuth - {{title}}", + "of {{count}} file(s) -——loaded——one": "dari {{count}} berkas -", + "of {{count}} file(s) -——loaded——other": "dari {{count}} berkas -", + "of {{count}} file(s) -——loading——one": "dari ... berkas(s) -", + "of {{count}} file(s) -——loading——other": "dari ... berkas(s) -", + "Oops, that's a <1>four-oh-four.": "Oops, itu adalah <1>empat-nol-empat.", + "Open URL": "Buka URL", + "Open URL{{url}}": "Buka URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "Tekan <2>F12 dan buka devtools untuk detail lebih lanjut, atau cari bantuan di <6>diskusi onedrive-vercel-index.", + "Proceed to OAuth": "Lanjut ke OAuth", + "Requesting tokens": "Meminta token", + "Restart": "Mulai Ulang", + "revisit home and do a hard refresh.": "kunjungi halaman utama lagi dan lakukan hard refresh.", + "Search ...": "Cari ...", + "Select all files": "Pilih semua berkas", + "Select file": "Pilih berkas", + "Select files": "Pilih berkas", + "Size": "Ukuran", + "Step 1/3: Preparations": "Langkah 1/3: Persiapan", + "Step 2/3: Get authorisation code": "Langkah 2/3: Mendapatkan kode otorisasi", + "Step 3/3: Get access and refresh tokens": "Langkah 3/3: Dapatkan akses dan perbarui token", + "Store tokens": "Simpan tokens", + "Stored! Going home...": "Tersimpan! Kembali ke halaman utama...", + "Storing tokens": "Menyimpan token", + "Success! The API returned what we needed.": "Sukses! API mengembalikan apa yang kami butuhkan.", + "The authorisation code extracted is:": "Kode otorisasi yang diekstrak adalah:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "Tautan OAuth untuk mendapatkan kode otorisasi telah dibuat. Klik link diatas untuk mendapatkan <2>kode otorisasi. Browser anda akan membuka tab baru ke halaman masuk akun Microsoft. Setelah masuk dan mengautentikasi dengan akun Microsoft, anda akan dialihkan ke halaman kosong di localhost. Tempel <6>semua URL yang dialihkan dibawah.", + "These tokens are used to authenticate yourself into password protected folders, ": "Token ini digunakan untuk mengautentikasi Anda ke folder yang dilindungi password,", + "These tokens may take a few seconds to populate after you click the button below. ": "Token ini mungkin memerlukan beberapa detik untuk muncul setelah Anda mengklik tombol dibawah.", + "This route (the folder itself and the files inside) is password protected. ": "Rute ini (folder dan berkas didalamnya) terkunci sandi. ", + "Unavailable": "Tidak tersedia", + "URL encoded": "URL terencode", + "Waiting for code...": "Menunggu kode...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "Selamat Datang di onedrive-vercel-index baru anda🎉", + "What is this?": "Apa ini?", + "Where is the auth code? Did you follow step 2 you silly donut?": "Mana kode otorisasinya? Apakah kamu mengikuti langkah 2 dasar kamu donat bodoh?", + "Whoops, looks like we got a problem: {{error}}.": "Waduh, sepertinya kita mendapatkan masalah : {{error}}." + } + diff --git a/public/locales/tr-TR/common.json b/public/locales/tr-TR/common.json new file mode 100644 index 0000000000000000000000000000000000000000..592612f2ffe894fa4edec36ac9b910e74f2519f1 --- /dev/null +++ b/public/locales/tr-TR/common.json @@ -0,0 +1,120 @@ +{ + "- showing {{count}} page(s) ——one": "- {{count}} sayfa gösteriliyor ", + "- showing {{count}} page(s) ——other": "- {{count}} sayfalar gösteriliyor ", + "{{count}} item(s)——one": "{{count}} öğe", + "{{count}} item(s)——other": "{{count}} öğeler", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> Bu web sitesinin sahibi değilseniz, şimdi durun, çünkü bu işleme devam etmek OneDrive'daki kişisel dosyalarınızı açığa çıkarabilir..", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> Vercel env değişkeninizin içinde bir REDIS_URL belirtmediyseniz, gidin ve bir tane başlatın. <3> Upstash . Docs: <6>Vercel Entegrasyonu - Upstash.", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> Eksik veya yanlış bir şey görürseniz, yeniden yapılandırmanız gerekir. <3>/config/api.config.js ve bu örneği yeniden dağıtın.", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ Artık bir sonraki adıma geçebilirsiniz: erişim tokeninizi isteyin ve tokeni yenileyin..", + "❌ No valid code extracted.": "❌ Geçerli Kod Çıkarılmadı.", + "Acquired access_token: ": "Alınan access_token: ", + "Acquired refresh_token: ": "Alınan refresh_token: ", + "Actions": "Hareketler", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "Geçerli olmadığı için yetkilendirme gerekli <2>access_token ya da <5>refresh_token bu dağıtılan örnekte mevcut. Onedrive-vercel-index'i kendi Microsoft hesabınızla yetkilendirmeye devam etmeden önce aşağıdaki yapılandırmaları kontrol edin.", + "Cancel": "Iptal", + "Cannot preview {{path}}": "{{path}} önizlenemiyor", + "Change the raw file direct link to a URL ending with the extension of the file.": "Ham dosyanın doğrudan bağlantısını, dosyanın uzantısıyla biten bir URL'ye değiştirin.", + "Check out <2>Microsoft's official explanation on the error message.": "Hata mesajıyla ilgili <2>Microsoft'un resmi açıklamasıni inceleyin.", + "Clear all": "Hepsini Sil", + "Clear all tokens?": "Tüm Tokenleri Sil ?", + "Cleared all tokens": "Tüm Tokenler Silindi..", + "clearing them means that you will need to re-enter the passwords again.": "Tokenleri temizlemek, şifreleri tekrar girmeniz gerekeceği anlamına gelir..", + "Copied direct link to clipboard.": "Panoya Bağlantı Kopyalandı.", + "Copied folder permalink.": "Kopyalanan Klasör Bağlantısı.", + "Copied raw file permalink.": "Kopyalanan Dosya Bağlantısı.", + "Copy direct link": "Doğrudan Bağlantıyı Kopyalayın", + "Copy folder permalink": "Klasör Bağlantısını Kopyala", + "Copy raw file permalink": "Ham dosya Bağlantısını Kopyala", + "Copy the permalink to the file to the clipboard": "Dosyanın Bağlantısını Kopyalayın", + "Customise direct link": "Doğrudan Bağlantıyı Ozelleştir", + "Customise link": "Bağlantıyı Ozelleştir", + "Customised": "Özelleştirilmiş", + "Customised and encoded": "Özelleştirilmiş ve Kodlanmış", + "Default": "Varsayılan", + "Do not pretend to be the site owner": "Site sahibiymiş gibi davranmayın..", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "Endişelenmeyin, bunları depoladıktan sonra onedrive-vercel-index siteniz yayına girdikten sonra belirteç yenilemeleri ve güncellemeleriyle ilgilenecektir..", + "Download": "Indir", + "Download file": "Dosyayı Indir", + "Download folder": "Klasörü Indir", + "Download selected files": "Seçili Dosyaları Indir", + "Download the file directly through OneDrive": "Dosyayı doğrudan OneDrive üzerinden indirin", + "Downloading {{progress}}%": "indiriliyor {{progress}}%", + "Downloading folder, refresh page to cancel": "Klasör indiriliyor, iptal etmek için sayfayı yenileyin", + "Downloading selected files, refresh page to cancel": "Seçili dosyalar indiriliyor, iptal etmek için sayfayı yenileyin", + "Downloading selected files...": "Seçilen Dosyalar Indiriliyor...", + "Email": "Mail", + "Enter Password": "Parola Gir", + "Error storing the token": "Token Yedeklenirken Hata Oluştu", + "Error validating identify, restart": "Tanımlama Doğrulanırken Hata Oluştu, Yeniden Başlat", + "Error: {{message}}": "Hata: {{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "Klasör indirilemedi {{path}}: {{status}} {{message}} Devam etmek için atlandı.", + "Failed to download folder.": "Klasor Indirme tamamlanmadı.", + "Failed to download selected files.": "Seçilen Dosyalar Indirilemedi.", + "File is empty.": "Dosya Boş.", + "File size": "Dosya Boyutu", + "Filename": "Dosya Adı", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "Son adım, {{minutes}} dakika {{seconds}} saniye sonra sona ermeden önce bu tokenleri kalıcı olarak saklamak için aşağıdaki düğmeye tıklayın.. ", + "Finished downloading folder.": "Klasor Indirme Tamamlandı.", + "Finished downloading selected files.": "Seçilen Dosyaların Indirilmesi Tamamlandı.", + "Get tokens": "Token Al", + "Grid": "Izgara", + "Hashes": "Karmalar", + "Home": "Anasayfa", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "Anasayfa'ya dönün ve yeniden kimlik doğrulamanızı söyleyen hoş geldiniz sayfasını görmeye devam edin, ", + "If you know the password, please enter it below.": "Şifreyi Biliyorsanız, lütfen Girin.", + "Last modified": "Son Düzenleme", + "Last Modified": "Son Düzenleme", + "Last modified:": "Son Düzenleme:", + "List": "Liste", + "Load more": "Daha Fazla Göster", + "Loading ...": "Yükleniyor ...", + "Loading EPUB ...": "EPUB Yükleniyor ...", + "Loading file content...": "Dosya içeriği Yükleniyor...", + "Loading FLV extension...": "FLV uzantısı Yükleniyor...", + "Logout": "Çıkış", + "MIME type": "Dosya Tipi", + "Name": "Ad", + "No more files": "Başka Dosya Yok", + "Nothing here.": "Burada Hiçbir Yok.", + "OAuth Step 1 - {{title}}": "OAuth Adım 1 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth Adım 2 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth Adım 3 - {{title}}", + "of {{count}} file(s) -——loaded——one": "{{count}} dosya -", + "of {{count}} file(s) -——loaded——other": "... {{count}} Dosyalar -", + "of {{count}} file(s) -——loading——one": "... {{count}} Dosya -", + "of {{count}} file(s) -——loading——other": "... Dosya(lar) -", + "Oops, that's a <1>four-oh-four.": "Hata, bu bir <1>four-oh-four Bilgileri Kontrol Et.", + "Open URL": "Link Aç", + "Open URL{{url}}": "URL'yi Aç {{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "Daha fazla ayrıntı için <2>F12 Basın Devtools'u açın veya şu adresten yardım isteyin: <6>Onedrive-vercel-index Tartışmalar.", + "Proceed to OAuth": "OAuth'a Devam Edin", + "Requesting tokens": "Token Isteniyor..", + "Restart": "Yeniden Başlat", + "revisit home and do a hard refresh.": "Anasayfa'ya Tekrar Gidin ve Yenileme Yapın.", + "Search ...": "Ara ...", + "Select all files": "Tüm dosyaları seç", + "Select file": "Dosya Seç", + "Select files": "Dosyaları seçin", + "Size": "Boyut", + "Step 1/3: Preparations": "Adım 1/3: Hazırlıklar", + "Step 2/3: Get authorisation code": "Adım 2/3: Yetkilendirme kodunu alın", + "Step 3/3: Get access and refresh tokens": "Adım 3/3: Erişim elde edin ve belirteçleri yenileyin", + "Store tokens": "Tokeni Saklayın", + "Stored! Going home...": "Yedeklendi! Anasayfa'ya Gidiliyor...", + "Storing tokens": "Token Yedeklendi", + "Success! The API returned what we needed.": "Başarılı! API ihtiyacımız Olan Değeri Döndürdü.", + "The authorisation code extracted is:": "Çıkarılan Yetkilendirme Kodu:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "Yetkilendirme kodunu almak için OAuth bağlantısı oluşturuldu. almak için yukarıdaki linke tıklayın <2>Yetkilendirme Kodu. Tarayıcınız, Microsoft'un hesap oturum açma sayfasına yeni bir sekme açacaktır. Giriş yaptıktan ve Microsoft hesabınızla kimlik doğrulaması yaptıktan sonra, localhost'ta boş bir sayfaya yönlendirileceksiniz. <6>Yönlendirilen URL'nin tamamını yapıştırın.", + "These tokens are used to authenticate yourself into password protected folders, ": "Bu tokenler, kendinizi parola korumalı klasörlerde doğrulamak için kullanılır, ", + "These tokens may take a few seconds to populate after you click the button below. ": "Aşağıdaki düğmeyi tıkladıktan sonra bu tokenlerin doldurulması birkaç saniye sürebilir. ", + "This route (the folder itself and the files inside) is password protected. ": "Klasör ve Içindeki Dosyalar Parola Ile Korumalıdır...", + "Unavailable": "Kullanım Dışı", + "URL encoded": "URL Kodlanmış", + "Waiting for code...": "Kod Bekleniyor...", + "Weibo": "Weibo", + "Welcome to your new onedrive-vercel-index 🎉": "Onedrive-vercel-index'inize hoş geldiniz 🎉", + "What is this?": "Bu Nedir?", + "Where is the auth code? Did you follow step 2 you silly donut?": "Yetkilendirme kodu nerede? 2. adımı izledin mi seni Aptal ?", + "Whoops, looks like we got a problem: {{error}}.": "Hata! Bir sorunumuz var gibi görünüyor: {{error}}." +} diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json new file mode 100644 index 0000000000000000000000000000000000000000..b12cd1489d10368bbcd0f9a371a353c5439ed7e3 --- /dev/null +++ b/public/locales/zh-CN/common.json @@ -0,0 +1,118 @@ +{ + "- showing {{count}} page(s) ——other": "已显示 {{count}} 页", + "{{count}} item(s)——other": "{{count}} 个项目", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> 如果你不是这个网站的所有者,请立即停止操作,因为接下来的操作可能会暴露你的 OneDrive 私人文件。", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> 如果你还没有在 Vercel 中设置环境变量 REDIS_URL,你可以从 <3>Upstash 处获取一个来使用。文档:<6>Vercel 集成 - Upstash。", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> 如果你看到有遗漏或错误的项目,你需要重新编辑 <3>/config/api.config.js 并重新部署这个实例。", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ 你现在可以进行下一步了:获取你的 access token 和 refresh token。", + "❌ No valid code extracted.": "❌ 无法提取授权码。", + "Acquired access_token: ": "获取 access_token", + "Acquired refresh_token: ": "获取 refresh_token", + "Actions": "操作", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "本项目还没有设置有效的 <2>access_token 和 <5>refresh_token,需要进行授权。在继续对 onedrive-vercel-index 授权你的 Microsoft 帐号前,请检查一下下方的配置信息。", + "Cancel": "取消", + "Cannot preview {{path}}": "无法预览 {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": "将文件直链接更改为以文件扩展名结尾的 URL。", + "Check out <2>Microsoft's official explanation on the error message.": "请查阅 <2>Microsoft 官方解释 以获取详细的错误信息。", + "Clear all": "清除所有密钥", + "Clear all tokens?": "清除所有密钥?", + "Cleared all tokens": "已清除所有密钥", + "clearing them means that you will need to re-enter the passwords again.": "清除它们意味着下次访问时你需要重新输入密钥。", + "Copied direct link to clipboard.": "已复制直链到剪贴板。", + "Copied folder permalink.": "已复制文件夹永久链接。", + "Copied raw file permalink.": "已复制文件永久链接。", + "Copy direct link": "复制文件直链", + "Copy folder permalink": "复制文件夹永久链接", + "Copy raw file permalink": "复制文件永久链接", + "Copy the permalink to the file to the clipboard": "复制文件永久链接到剪贴板", + "Customise direct link": "自定义文件直链", + "Customise link": "自定义直链", + "Customised": "自定义链接", + "Customised and encoded": "URL 编码的自定义链接", + "Copy selected files permalink": "复制选定文件永久链接", + "Copied selected files permalink.": "已复制选定文件永久链接。", + "Default": "默认", + "Do not pretend to be the site owner": "你不是网站所有者", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "别担心,存储它们之后,onedrive-vercel-index 会在帮助你定时更新 token", + "Download": "下载", + "Download file": "下载此文件", + "Download folder": "下载此文件夹", + "Download selected files": "下载选定文件", + "Download the file directly through OneDrive": "直接从 OneDrive 下载文件", + "Downloading {{progress}}%": "已下载 {{progress}}%", + "Downloading folder, refresh page to cancel": "下载文件夹中,刷新页面以取消", + "Downloading selected files, refresh page to cancel": "下载选定文件中,刷新页面以取消", + "Downloading selected files...": "下载选定文件中…", + "Email": "电子邮件", + "Enter Password": "输入密码", + "Error storing the token": "存储 token 时出错", + "Error validating identify, restart": "校验身份出错,需要重新开始", + "Error: {{message}}": "错误:{{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "下载文件夹 {{path}} 失败:{{status}} {{message}} 已忽略此错误并继续下载。", + "Failed to download folder.": "下载文件夹失败。", + "Failed to download selected files.": "下载选定文件失败。", + "File is empty.": "文件为空。", + "File size": "文件大小", + "Filename": "文件名", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "最后一步,在这些 tokens 于 {{minutes}} 分钟 {{seconds}} 秒后失效前,点击下方按钮以永久存储这些 tokens。", + "Finished downloading folder.": "下载文件夹成功。", + "Finished downloading selected files.": "下载选定文件成功。", + "Get tokens": "获取 tokens", + "Grid": "图格", + "Hashes": "哈希值", + "Home": "首页", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "如果你回到首页却仍然发现欢迎界面在提示你重新认证,", + "If you know the password, please enter it below.": "如果你知晓密码,请在下面输入。", + "Last modified": "最后修改时间", + "Last Modified": "最后修改时间", + "Last modified:": "最后修改时间:", + "List": "列表", + "Load more": "加载更多", + "Loading ...": "加载中…", + "Loading EPUB ...": "加载 EPUB 中…", + "Loading file content...": "加载文件内容中…", + "Loading FLV extension...": "加载 FLV 扩展中…", + "Logout": "注销", + "MIME type": "MIME 类型", + "Name": "文件名", + "No more files": "加载完毕", + "Nothing here.": "无内容。", + "OAuth Step 1 - {{title}}": "OAuth 第 1 步 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth 第 2 步 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth 第 3 步 - {{title}}", + "of {{count}} file(s) -——loaded——other": "共 {{count}} 个文件", + "of {{count}} file(s) -——loading——other": "共…个文件", + "Oops, that's a <1>four-oh-four.": "Oops,这里是 <1>404 页面。", + "Open URL": "打开 URL", + "Open URL{{url}}": "打开 URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "请按下 <2>F12 来打开开发者工具窗口以获取详细信息,或是到 <6>onedrive-vercel-index 社区讨论 处寻求帮助。", + "Proceed to OAuth": "继续进行 OAuth", + "Requesting tokens": "正在获取 token", + "Restart": "重新开始", + "revisit home and do a hard refresh.": "重新访问首页并刷新浏览器", + "Search ...": "搜索…", + "Select all files": "选择所有文件", + "Select file": "选择此文件", + "Select files": "选择以下文件", + "Size": "文件大小", + "Step 1/3: Preparations": "步骤 1/3:准备", + "Step 2/3: Get authorisation code": "步骤 2/3:获取授权码", + "Step 3/3: Get access and refresh tokens": "步骤 3/3:获取 access token 和 refresh token", + "Store tokens": "储存 tokens", + "Stored! Going home...": "已存储!正在返回首页…", + "Storing tokens": "正在存储 token…", + "Success! The API returned what we needed.": "成功!需要的 token 已被返回。", + "The authorisation code extracted is:": "提取出的授权码为:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "创建出的这个 OAuth 链接是用来获取授权码的。点击上方链接以获取所需的 <2>授权码。你的浏览器将在新的标签页打开 Microsoft 帐号登录页面。在登录并验证你的 Microsoft 帐号之后,你将被重定向到一个域名为 localhost 的空白页面。请将<6>完整的重定向后的 URL 整体复制粘贴到下方。", + "These tokens are used to authenticate yourself into password protected folders, ": "这些密钥是用来验证你的身份以访问密钥保护下的文件夹的,", + "These tokens may take a few seconds to populate after you click the button below. ": "当你点击下方按钮之后,这些 tokens 可能需要几秒钟来生成出现。", + "This route (the folder itself and the files inside) is password protected. ": "此路由(此文件夹和其中的文件)是受密钥保护的。", + "Unavailable": "无", + "URL encoded": "URL 编码的链接", + "Waiting for code...": "等待授权码…", + "Weibo": "微博", + "Welcome to your new onedrive-vercel-index 🎉": "欢迎来到你崭新的 onedrive-vercel-index 🎉", + "What is this?": "这是什么?", + "Where is the auth code? Did you follow step 2 you silly donut?": "授权码呢?你遵守了第 2 步吗?你这个傻瓜甜甜圈!o( ̄ヘ ̄o#)", + "Whoops, looks like we got a problem: {{error}}.": "Whoops,看来我们遇到了一个问题:{{error}}" +} diff --git a/public/locales/zh-TW/common.json b/public/locales/zh-TW/common.json new file mode 100644 index 0000000000000000000000000000000000000000..6b6223a6ddb41081ab7e8bb5019a6fd933bea91f --- /dev/null +++ b/public/locales/zh-TW/common.json @@ -0,0 +1,116 @@ +{ + "- showing {{count}} page(s) ——other": "已顯示 {{count}} 頁", + "{{count}} item(s)——other": "{{count}} 個檔案", + "<0> If you are not the owner of this website, stop now, as continuing with this process may expose your personal files in OneDrive.": "<0> 如果你不是這網站的擁有者,請立即停止操作,因為接下來的動作可能造成你的 OneDrive 私人檔案外洩。", + "<0> If you have not specified a REDIS_URL inside your Vercel env variable, go initialise one at <3>Upstash. Docs: <6>Vercel Integration - Upstash.": "<0> 如果你還沒在 Vercel 中設置環境變數 REDIS_URL,你可以從 <3>Upstash 取得一個 REDIS 來使用。操作說明:<6>Vercel 集成 - Upstash。", + "<0> If you see anything missing or incorrect, you need to reconfigure <3>/config/api.config.js and redeploy this instance.": "<0> 如果你看到有遺漏或錯誤的資訊,你需要重新編輯 <3>/config/api.config.js 並重新佈署。", + "✅ You can now proceed onto the next step: requesting your access token and refresh token.": "✅ 你現在可以進行下一步了:取得你的 access token 及 refresh token。", + "❌ No valid code extracted.": "❌ 無法取得授權碼。", + "Acquired access_token: ": "取得 access_token", + "Acquired refresh_token: ": "取得 refresh_token", + "Actions": "操作", + "Authorisation is required as no valid <2>access_token or <5>refresh_token is present on this deployed instance. Check the following configurations before proceeding with authorising onedrive-vercel-index with your own Microsoft account.": "系統還沒設定有效的 <2>access_token 及 <5>refresh_token,需要進行授權。在繼續對 onedrive-vercel-index 授權你的 Microsoft 帳號前,請檢查下方的配置設定是否正確。", + "Cancel": "取消", + "Cannot preview {{path}}": "無法預覽 {{path}}", + "Change the raw file direct link to a URL ending with the extension of the file.": "將檔案的直接下載連結更改為以文件附檔名結尾的 URL。", + "Check out <2>Microsoft's official explanation on the error message.": "請查看 <2>Microsoft 官方說明 來取得詳細的錯誤資訊。", + "Clear all": "清除所有密碼", + "Clear all tokens?": "清除所有密碼?", + "Cleared all tokens": "已清除所有密碼", + "clearing them means that you will need to re-enter the passwords again.": "清除後代表下次訪問時需要重新輸入密碼。", + "Copied direct link to clipboard.": "已複製直接下載的連結置剪貼簿。", + "Copied folder permalink.": "已複製資料夾的永久連結。", + "Copied raw file permalink.": "已複製檔案的永久連結。", + "Copy direct link": "複製直接下載的連結", + "Copy folder permalink": "複製資料夾的永久連結", + "Copy raw file permalink": "複製檔案的永久連結", + "Copy the permalink to the file to the clipboard": "複製檔案的永久連結到剪貼簿", + "Customise direct link": "自訂檔案直接下載連結", + "Customise link": "自訂直接下載連結", + "Customised": "自訂下載連結", + "Customised and encoded": "URL 編碼的自訂連結", + "Default": "預設", + "Do not pretend to be the site owner": "你不是網站的擁有者", + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live.": "不用擔心,保存 token 之後,onedrive-vercel-index 會定期的幫助你更新 token", + "Download": "下載", + "Download file": "下載此檔案", + "Download folder": "下載此資料夾", + "Download selected files": "下載已選擇檔案", + "Download the file directly through OneDrive": "直接從 OneDrive 下載檔案", + "Downloading {{progress}}%": "已下載 {{progress}}%", + "Downloading folder, refresh page to cancel": "下載資料夾中,重新整理頁面將取消下載", + "Downloading selected files, refresh page to cancel": "下載選擇的檔案中,重新整理頁面將取消下載", + "Downloading selected files...": "下載選擇的檔案中…", + "Email": "電子郵件", + "Enter Password": "輸入密碼", + "Error storing the token": "儲存 token 時出現錯誤", + "Error validating identify, restart": "驗證身分時出現錯誤,請重新開始", + "Error: {{message}}": "錯誤:{{message}}", + "Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.": "{{path}} 資料夾下載失敗:{{status}} {{message}} 已略過此錯誤並繼續下載。", + "Failed to download folder.": "資料夾下載失敗。", + "Failed to download selected files.": "您選擇的檔案下載失敗。", + "File is empty.": "資料夾是空的。", + "File size": "檔案大小", + "Filename": "檔案名稱", + "Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ": "最後一步,在這些 tokens 於 {{minutes}} 分鐘 {{seconds}} 秒後失效前,點擊下方的按鈕來保存這個 tokens。", + "Finished downloading folder.": "下載資料夾成功。", + "Finished downloading selected files.": "下載選擇的檔案成功。", + "Get tokens": "取得 tokens", + "Grid": "圖示", + "Hashes": "hash", + "Home": "首頁", + "If you go back home and still see the welcome page telling you to re-authenticate, ": "如果你回到首頁卻還是看到歡迎畫面代表你需要重新驗證,", + "If you know the password, please enter it below.": "如果你知道密碼,請在下面輸入。", + "Last modified": "最後修改時間", + "Last Modified": "最後修改時間", + "Last modified:": "最後修改時間:", + "List": "列表", + "Load more": "載入更多", + "Loading ...": "載入中…", + "Loading EPUB ...": "載入 EPUB 中…", + "Loading file content...": "載入檔案內容中…", + "Loading FLV extension...": "載入 FLV 擴充工具中…", + "Logout": "登出", + "MIME type": "MIME 檔案類型", + "Name": "檔案名稱", + "No more files": "載入完畢", + "Nothing here.": "沒有內容。", + "OAuth Step 1 - {{title}}": "OAuth 第 1 步 - {{title}}", + "OAuth Step 2 - {{title}}": "OAuth 第 2 步 - {{title}}", + "OAuth Step 3 - {{title}}": "OAuth 第 3 步 - {{title}}", + "of {{count}} file(s) -——loaded——other": "共 {{count}} 個檔案", + "of {{count}} file(s) -——loading——other": "共…個檔案", + "Oops, that's a <1>four-oh-four.": "Oops,這裡是 <1>404 頁面。", + "Open URL": "開啟 URL", + "Open URL{{url}}": "開啟 URL{{url}}", + "Press <2>F12 and open devtools for more details, or seek help at <6>onedrive-vercel-index discussions.": "請按下 <2>F12 開啟開發人員選項取得詳細資訊,或是前往 <6>onedrive-vercel-index Github 討論區 尋找解答。", + "Proceed to OAuth": "繼續進行 OAuth", + "Requesting tokens": "正在取得 token", + "Restart": "重新開始", + "revisit home and do a hard refresh.": "回到首頁並重新整理", + "Search ...": "搜尋…", + "Select all files": "選擇所有檔案", + "Select file": "選擇此檔案", + "Select files": "選擇以下檔案", + "Size": "檔案大小", + "Step 1/3: Preparations": "步驟 1/3:準備", + "Step 2/3: Get authorisation code": "步驟 2/3:取得授權碼", + "Step 3/3: Get access and refresh tokens": "步驟 3/3:取得 access token 及 refresh token", + "Store tokens": "儲存 tokens", + "Stored! Going home...": "已儲存!正在返回首頁…", + "Storing tokens": "正在儲存 token…", + "Success! The API returned what we needed.": "需要的 token 已成功取得!", + "The authorisation code extracted is:": "取得的授權碼為:", + "The OAuth link for getting the authorisation code has been created. Click on the link above to get the <2>authorisation code. Your browser willopen a new tab to Microsoft's account login page. After logging in and authenticating with your Microsoft account, you will be redirected to a blank page on localhost. Paste <6>the entire redirected URL down below.": "創建出的這個 OAuth 連結是用來取得授權碼的。點選上方連結來取得所需的 <2>授權碼。你的瀏覽器會開啟新分頁打開 Microsoft 帳號登入畫面。在登入並驗證你的 Microsoft 帳號之後,你會被導向到一個域名叫做 localhost 的空白頁面。請將此空白頁面上<6>完整的網址 URL 複製並貼到下方。", + "These tokens are used to authenticate yourself into password protected folders, ": "這些 token 是用來確認你的身分,讓你可以開啟受密碼保護的資料夾,", + "These tokens may take a few seconds to populate after you click the button below. ": "當你點選下方按鈕後,這些 tokens 可能需要花幾秒鐘來產生。", + "This route (the folder itself and the files inside) is password protected. ": "此路徑(此資料夾及資料夾內的檔案)有密碼保護。", + "Unavailable": "無", + "URL encoded": "URL 編碼的連結", + "Waiting for code...": "等待授權碼…", + "Weibo": "微博", + "Welcome to your new onedrive-vercel-index 🎉": "歡迎來到全新的 onedrive-vercel-index 🎉", + "What is this?": "這是什麼?", + "Where is the auth code? Did you follow step 2 you silly donut?": "授權碼在哪裡?你有跟著第 2 步完成了嗎?o( ̄ヘ ̄o#)", + "Whoops, looks like we got a problem: {{error}}.": "Whoops,看來現在遇到一些問題:{{error}}" +} diff --git a/public/players/iina.png b/public/players/iina.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4f367445d9540a12be1b083a86993001feccf6 Binary files /dev/null and b/public/players/iina.png differ diff --git a/public/players/nplayer.png b/public/players/nplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..9701dadc69a15da28510c34af7ae2ab40a31be3e Binary files /dev/null and b/public/players/nplayer.png differ diff --git a/public/players/potplayer.png b/public/players/potplayer.png new file mode 100644 index 0000000000000000000000000000000000000000..6581549d0cd4b2b9315c85811cd26c5529d9b3eb Binary files /dev/null and b/public/players/potplayer.png differ diff --git a/public/players/vlc.png b/public/players/vlc.png new file mode 100644 index 0000000000000000000000000000000000000000..45d027d2401ac1396ebc00e4fdb93dac1c1cd159 Binary files /dev/null and b/public/players/vlc.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000000000000000000000000000000000000..b20abb7cbb2903c45280ba3540710669aeb63163 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000000000000000000000000000000000..a2f727c5d9200164bd2d26eb86bb3f82aba5c836 --- /dev/null +++ b/renovate.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base", + ":semanticCommits" + ], + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch" + ], + "matchCurrentVersion": "!/^0/", + "automerge": true + } + ] +} diff --git a/src/components/Auth.tsx b/src/components/Auth.tsx new file mode 100644 index 0000000000000000000000000000000000000000..094bbb2c5027363bdb988e937b9a08986e6b392f --- /dev/null +++ b/src/components/Auth.tsx @@ -0,0 +1,63 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +import Image from 'next/image' +import { useRouter } from 'next/router' +import { FC, useState } from 'react' +import { useTranslation } from 'next-i18next' + +import { matchProtectedRoute } from '../utils/protectedRouteHandler' +import useLocalStorage from '../utils/useLocalStorage' + +const Auth: FC<{ redirect: string }> = ({ redirect }) => { + const authTokenPath = matchProtectedRoute(redirect) + + const router = useRouter() + const [token, setToken] = useState('') + const [_, setPersistedToken] = useLocalStorage(authTokenPath, '') + + const { t } = useTranslation() + + return ( +
+
+ authenticate +
+
{t('Enter Password')}
+ +

+ {t('This route (the folder itself and the files inside) is password protected. ') + + t('If you know the password, please enter it below.')} +

+ +
+ { + setToken(e.target.value) + }} + onKeyPress={e => { + if (e.key === 'Enter' || e.key === 'NumpadEnter') { + setPersistedToken(token) + router.reload() + } + }} + /> + +
+
+ ) +} + +export default Auth diff --git a/src/components/Breadcrumb.tsx b/src/components/Breadcrumb.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4a54e0fb64bf0861d64c4e796044ca080574e0d7 --- /dev/null +++ b/src/components/Breadcrumb.tsx @@ -0,0 +1,61 @@ +import type { ParsedUrlQuery } from 'querystring' + +import Link from 'next/link' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation } from 'next-i18next' + +const HomeCrumb = () => { + const { t } = useTranslation() + + return ( + + + {t('Home')} + + ) +} + +const Breadcrumb: React.FC<{ query?: ParsedUrlQuery }> = ({ query }) => { + if (query) { + const { path } = query + if (Array.isArray(path)) { + // We are rendering the path in reverse, so that the browser automatically scrolls to the end of the breadcrumb + // https://stackoverflow.com/questions/18614301/keep-overflow-div-scrolled-to-bottom-unless-user-scrolls-up/18614561 + return ( +
    + {path + .slice(0) + .reverse() + .map((p: string, i: number) => ( +
  1. + + encodeURIComponent(p)) + .join('/')}`} + passHref + className={`ml-1 transition-all duration-75 hover:opacity-70 md:ml-3 ${ + i == 0 && 'pointer-events-none opacity-80' + }`} + > + {p} + +
  2. + ))} +
  3. + +
  4. +
+ ) + } + } + + return ( +
+ +
+ ) +} + +export default Breadcrumb diff --git a/src/components/CustomEmbedLinkMenu.tsx b/src/components/CustomEmbedLinkMenu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..862ae6801f8fce584e29d2796edb09d4e1da0418 --- /dev/null +++ b/src/components/CustomEmbedLinkMenu.tsx @@ -0,0 +1,131 @@ +import { Dispatch, Fragment, SetStateAction, useRef, useState } from 'react' +import { useTranslation } from 'next-i18next' +import { Dialog, Transition } from '@headlessui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useClipboard } from 'use-clipboard-copy' + +import { getBaseUrl } from '../utils/getBaseUrl' +import { getStoredToken } from '../utils/protectedRouteHandler' +import { getReadablePath } from '../utils/getReadablePath' + +function LinkContainer({ title, value }: { title: string; value: string }) { + const clipboard = useClipboard({ copiedTimeout: 1000 }) + return ( + <> +

{title}

+
+
{value}
+ +
+ + ) +} + +export default function CustomEmbedLinkMenu({ + path, + menuOpen, + setMenuOpen, +}: { + path: string + menuOpen: boolean + setMenuOpen: Dispatch> +}) { + const { t } = useTranslation() + + const hashedToken = getStoredToken(path) + + // Focus on input automatically when menu modal opens + const focusInputRef = useRef(null) + const closeMenu = () => setMenuOpen(false) + + const readablePath = getReadablePath(path) + const filename = readablePath.substring(readablePath.lastIndexOf('/') + 1) + const [name, setName] = useState(filename) + + return ( + + +
+ + + + + {/* This element is to trick the browser into centering the modal contents. */} + + +
+ + {t('Customise direct link')} + + + <> + {t('Change the raw file direct link to a URL ending with the extension of the file.')}{' '} + + {t('What is this?')} + + + + +
+

{t('Filename')}

+ setName(e.target.value)} + /> + + + + + +
+
+
+
+
+
+ ) +} diff --git a/src/components/DownloadBtnGtoup.tsx b/src/components/DownloadBtnGtoup.tsx new file mode 100644 index 0000000000000000000000000000000000000000..03078a98c3626afc9bd4095b6928ef8361eff9be --- /dev/null +++ b/src/components/DownloadBtnGtoup.tsx @@ -0,0 +1,106 @@ +import { MouseEventHandler, useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { IconProp } from '@fortawesome/fontawesome-svg-core' +import toast from 'react-hot-toast' +import { useClipboard } from 'use-clipboard-copy' +import { useTranslation } from 'next-i18next' + +import Image from 'next/image' +import { useRouter } from 'next/router' + +import { getBaseUrl } from '../utils/getBaseUrl' +import { getStoredToken } from '../utils/protectedRouteHandler' +import CustomEmbedLinkMenu from './CustomEmbedLinkMenu' + +const btnStyleMap = (btnColor?: string) => { + const colorMap = { + gray: 'hover:text-gray-600 dark:hover:text-white focus:ring-gray-200 focus:text-gray-600 dark:focus:text-white border-gray-300 dark:border-gray-500 dark:focus:ring-gray-500', + blue: 'hover:text-blue-600 focus:ring-blue-200 focus:text-blue-600 border-blue-300 dark:border-blue-700 dark:focus:ring-blue-500', + teal: 'hover:text-teal-600 focus:ring-teal-200 focus:text-teal-600 border-teal-300 dark:border-teal-700 dark:focus:ring-teal-500', + red: 'hover:text-red-600 focus:ring-red-200 focus:text-red-600 border-red-300 dark:border-red-700 dark:focus:ring-red-500', + green: + 'hover:text-green-600 focus:ring-green-200 focus:text-green-600 border-green-300 dark:border-green-700 dark:focus:ring-green-500', + pink: 'hover:text-pink-600 focus:ring-pink-200 focus:text-pink-600 border-pink-300 dark:border-pink-700 dark:focus:ring-pink-500', + yellow: + 'hover:text-yellow-400 focus:ring-yellow-100 focus:text-yellow-400 border-yellow-300 dark:border-yellow-400 dark:focus:ring-yellow-300', + } + + if (btnColor) { + return colorMap[btnColor] + } + + return colorMap.gray +} + +export const DownloadButton = ({ + onClickCallback, + btnColor, + btnText, + btnIcon, + btnImage, + btnTitle, +}: { + onClickCallback: MouseEventHandler + btnColor?: string + btnText: string + btnIcon?: IconProp + btnImage?: string + btnTitle?: string +}) => { + return ( + + ) +} + +const DownloadButtonGroup = () => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + const clipboard = useClipboard() + const [menuOpen, setMenuOpen] = useState(false) + + const { t } = useTranslation() + + return ( + <> + +
+ window.open(`/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}`)} + btnColor="blue" + btnText={t('Download')} + btnIcon="file-download" + btnTitle={t('Download the file directly through OneDrive')} + /> + { + clipboard.copy(`${getBaseUrl()}/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}`) + toast.success(t('Copied direct link to clipboard.')) + }} + btnColor="pink" + btnText={t('Copy direct link')} + btnIcon="copy" + btnTitle={t('Copy the permalink to the file to the clipboard')} + /> + setMenuOpen(true)} + btnColor="teal" + btnText={t('Customise link')} + btnIcon="pen" + /> +
+ + ) +} + +export default DownloadButtonGroup diff --git a/src/components/FileListing.tsx b/src/components/FileListing.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2532d691696545f8e157cfcfc6916b43fd75adef --- /dev/null +++ b/src/components/FileListing.tsx @@ -0,0 +1,442 @@ +import type { OdFileObject, OdFolderChildren, OdFolderObject } from '../types' +import { ParsedUrlQuery } from 'querystring' +import { FC, MouseEventHandler, SetStateAction, useEffect, useRef, useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import toast, { Toaster } from 'react-hot-toast' +import emojiRegex from 'emoji-regex' + +import dynamic from 'next/dynamic' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import useLocalStorage from '../utils/useLocalStorage' +import { getPreviewType, preview } from '../utils/getPreviewType' +import { useProtectedSWRInfinite } from '../utils/fetchWithSWR' +import { getExtension, getRawExtension, getFileIcon } from '../utils/getFileIcon' +import { getStoredToken } from '../utils/protectedRouteHandler' +import { + DownloadingToast, + downloadMultipleFiles, + downloadTreelikeMultipleFiles, + traverseFolder, +} from './MultiFileDownloader' + +import { layouts } from './SwitchLayout' +import Loading, { LoadingIcon } from './Loading' +import FourOhFour from './FourOhFour' +import Auth from './Auth' +import TextPreview from './previews/TextPreview' +import MarkdownPreview from './previews/MarkdownPreview' +import CodePreview from './previews/CodePreview' +import OfficePreview from './previews/OfficePreview' +import AudioPreview from './previews/AudioPreview' +import VideoPreview from './previews/VideoPreview' +import PDFPreview from './previews/PDFPreview' +import URLPreview from './previews/URLPreview' +import ImagePreview from './previews/ImagePreview' +import DefaultPreview from './previews/DefaultPreview' +import { PreviewContainer } from './previews/Containers' + +import FolderListLayout from './FolderListLayout' +import FolderGridLayout from './FolderGridLayout' + +// Disabling SSR for some previews +const EPUBPreview = dynamic(() => import('./previews/EPUBPreview'), { + ssr: false, +}) + +/** + * Convert url query into path string + * + * @param query Url query property + * @returns Path string + */ +const queryToPath = (query?: ParsedUrlQuery) => { + if (query) { + const { path } = query + if (!path) return '/' + if (typeof path === 'string') return `/${encodeURIComponent(path)}` + return `/${path.map(p => encodeURIComponent(p)).join('/')}` + } + return '/' +} + +// Render the icon of a folder child (may be a file or a folder), use emoji if the name of the child contains emoji +const renderEmoji = (name: string) => { + const emoji = emojiRegex().exec(name) + return { render: emoji && !emoji.index, emoji } +} +const formatChildName = (name: string) => { + const { render, emoji } = renderEmoji(name) + return render ? name.replace(emoji ? emoji[0] : '', '').trim() : name +} +export const ChildName: FC<{ name: string; folder?: boolean }> = ({ name, folder }) => { + const original = formatChildName(name) + const extension = folder ? '' : getRawExtension(original) + const prename = folder ? original : original.substring(0, original.length - extension.length) + return ( + + {prename} + + ) +} +export const ChildIcon: FC<{ child: OdFolderChildren }> = ({ child }) => { + const { render, emoji } = renderEmoji(child.name) + return render ? ( + {emoji ? emoji[0] : '📁'} + ) : ( + + ) +} + +export const Checkbox: FC<{ + checked: 0 | 1 | 2 + onChange: () => void + title: string + indeterminate?: boolean +}> = ({ checked, onChange, title, indeterminate }) => { + const ref = useRef(null) + + useEffect(() => { + if (ref.current) { + ref.current.checked = Boolean(checked) + if (indeterminate) { + ref.current.indeterminate = checked == 1 + } + } + }, [ref, checked, indeterminate]) + + const handleClick: MouseEventHandler = e => { + if (ref.current) { + if (e.target === ref.current) { + e.stopPropagation() + } else { + ref.current.click() + } + } + } + + return ( + + + + ) +} + +export const Downloading: FC<{ title: string; style: string }> = ({ title, style }) => { + return ( + + + + ) +} + +const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => { + const [selected, setSelected] = useState<{ [key: string]: boolean }>({}) + const [totalSelected, setTotalSelected] = useState<0 | 1 | 2>(0) + const [totalGenerating, setTotalGenerating] = useState(false) + const [folderGenerating, setFolderGenerating] = useState<{ + [key: string]: boolean + }>({}) + + const router = useRouter() + const hashedToken = getStoredToken(router.asPath) + const [layout, _] = useLocalStorage('preferredLayout', layouts[0]) + + const { t } = useTranslation() + + const path = queryToPath(query) + + const { data, error, size, setSize } = useProtectedSWRInfinite(path) + + if (error) { + // If error includes 403 which means the user has not completed initial setup, redirect to OAuth page + if (error.status === 403) { + router.push('/onedrive-vercel-index-oauth/step-1') + return
+ } + + return ( + + {error.status === 401 ? : } + + ) + } + if (!data) { + return ( + + + + ) + } + + const responses: any[] = data ? [].concat(...data) : [] + + const isLoadingInitialData = !data && !error + const isLoadingMore = isLoadingInitialData || (size > 0 && data && typeof data[size - 1] === 'undefined') + const isEmpty = data?.[0]?.length === 0 + const isReachingEnd = isEmpty || (data && typeof data[data.length - 1]?.next === 'undefined') + const onlyOnePage = data && typeof data[0].next === 'undefined' + + if ('folder' in responses[0]) { + // Expand list of API returns into flattened file data + const folderChildren = [].concat(...responses.map(r => r.folder.value)) as OdFolderObject['value'] + + // Find README.md file to render + const readmeFile = folderChildren.find(c => c.name.toLowerCase() === 'readme.md') + + // Filtered file list helper + const getFiles = () => folderChildren.filter(c => !c.folder && c.name !== '.password') + + // File selection + const genTotalSelected = (selected: { [key: string]: boolean }) => { + const selectInfo = getFiles().map(c => Boolean(selected[c.id])) + const [hasT, hasF] = [selectInfo.some(i => i), selectInfo.some(i => !i)] + return hasT && hasF ? 1 : !hasF ? 2 : 0 + } + + const toggleItemSelected = (id: string) => { + let val: SetStateAction<{ [key: string]: boolean }> + if (selected[id]) { + val = { ...selected } + delete val[id] + } else { + val = { ...selected, [id]: true } + } + setSelected(val) + setTotalSelected(genTotalSelected(val)) + } + + const toggleTotalSelected = () => { + if (genTotalSelected(selected) == 2) { + setSelected({}) + setTotalSelected(0) + } else { + setSelected(Object.fromEntries(getFiles().map(c => [c.id, true]))) + setTotalSelected(2) + } + } + + // Selected file download + const handleSelectedDownload = () => { + const folderName = path.substring(path.lastIndexOf('/') + 1) + const folder = folderName ? decodeURIComponent(folderName) : undefined + const files = getFiles() + .filter(c => selected[c.id]) + .map(c => ({ + name: c.name, + url: `/api/raw/?path=${path}/${encodeURIComponent(c.name)}${hashedToken ? `&odpt=${hashedToken}` : ''}`, + })) + + if (files.length == 1) { + const el = document.createElement('a') + el.style.display = 'none' + document.body.appendChild(el) + el.href = files[0].url + el.click() + el.remove() + } else if (files.length > 1) { + setTotalGenerating(true) + + const toastId = toast.loading() + downloadMultipleFiles({ toastId, router, files, folder }) + .then(() => { + setTotalGenerating(false) + toast.success(t('Finished downloading selected files.'), { + id: toastId, + }) + }) + .catch(() => { + setTotalGenerating(false) + toast.error(t('Failed to download selected files.'), { id: toastId }) + }) + } + } + + // Get selected file permalink + const handleSelectedPermalink = (baseUrl: string) => { + return getFiles() + .filter(c => selected[c.id]) + .map( + c => + `${baseUrl}/api/raw/?path=${path}/${encodeURIComponent(c.name)}${hashedToken ? `&odpt=${hashedToken}` : ''}` + ) + .join('\n') + } + + // Folder recursive download + const handleFolderDownload = (path: string, id: string, name?: string) => () => { + const files = (async function* () { + for await (const { meta: c, path: p, isFolder, error } of traverseFolder(path)) { + if (error) { + toast.error( + t('Failed to download folder {{path}}: {{status}} {{message}} Skipped it to continue.', { + path: p, + status: error.status, + message: error.message, + }) + ) + continue + } + const hashedTokenForPath = getStoredToken(p) + yield { + name: c?.name, + url: `/api/raw/?path=${p}${hashedTokenForPath ? `&odpt=${hashedTokenForPath}` : ''}`, + path: p, + isFolder, + } + } + })() + + setFolderGenerating({ ...folderGenerating, [id]: true }) + const toastId = toast.loading() + + downloadTreelikeMultipleFiles({ + toastId, + router, + files, + basePath: path, + folder: name, + }) + .then(() => { + setFolderGenerating({ ...folderGenerating, [id]: false }) + toast.success(t('Finished downloading folder.'), { id: toastId }) + }) + .catch(() => { + setFolderGenerating({ ...folderGenerating, [id]: false }) + toast.error(t('Failed to download folder.'), { id: toastId }) + }) + } + + // Folder layout component props + const folderProps = { + toast, + path, + folderChildren, + selected, + toggleItemSelected, + totalSelected, + toggleTotalSelected, + totalGenerating, + handleSelectedDownload, + folderGenerating, + handleSelectedPermalink, + handleFolderDownload, + } + + return ( + <> + + + {layout.name === 'Grid' ? : } + + {!onlyOnePage && ( +
+
+ {t('- showing {{count}} page(s) ', { + count: size, + totalFileNum: isLoadingMore ? '...' : folderChildren.length, + }) + + (isLoadingMore + ? t('of {{count}} file(s) -', { count: folderChildren.length, context: 'loading' }) + : t('of {{count}} file(s) -', { count: folderChildren.length, context: 'loaded' }))} +
+ +
+ )} + + {readmeFile && ( +
+ +
+ )} + + ) + } + + if ('file' in responses[0] && responses.length === 1) { + const file = responses[0].file as OdFileObject + const previewType = getPreviewType(getExtension(file.name), { video: Boolean(file.video) }) + + if (previewType) { + switch (previewType) { + case preview.image: + return + + case preview.text: + return + + case preview.code: + return + + case preview.markdown: + return + + case preview.video: + return + + case preview.audio: + return + + case preview.pdf: + return + + case preview.office: + return + + case preview.epub: + return + + case preview.url: + return + + default: + return + } + } else { + return + } + } + + return ( + + + + ) +} +export default FileListing diff --git a/src/components/FolderGridLayout.tsx b/src/components/FolderGridLayout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..98d764704567ff8754ea594864bfe1e6d7aef5c0 --- /dev/null +++ b/src/components/FolderGridLayout.tsx @@ -0,0 +1,200 @@ +import type { OdFolderChildren } from '../types' + +import Link from 'next/link' +import { useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useClipboard } from 'use-clipboard-copy' +import { useTranslation } from 'next-i18next' + +import { getBaseUrl } from '../utils/getBaseUrl' +import { formatModifiedDateTime } from '../utils/fileDetails' +import { Checkbox, ChildIcon, ChildName, Downloading } from './FileListing' +import { getStoredToken } from '../utils/protectedRouteHandler' + +const GridItem = ({ c, path }: { c: OdFolderChildren; path: string }) => { + // We use the generated medium thumbnail for rendering preview images (excluding folders) + const hashedToken = getStoredToken(path) + const thumbnailUrl = + 'folder' in c ? null : `/api/thumbnail/?path=${path}&size=medium${hashedToken ? `&odpt=${hashedToken}` : ''}` + + // Some thumbnails are broken, so we check for onerror event in the image component + const [brokenThumbnail, setBrokenThumbnail] = useState(false) + + return ( +
+
+ {thumbnailUrl && !brokenThumbnail ? ( + // eslint-disable-next-line @next/next/no-img-element + {c.name} setBrokenThumbnail(true)} + /> + ) : ( +
+ + + {c.folder?.childCount} + +
+ )} +
+ +
+ + + + +
+
+ {formatModifiedDateTime(c.lastModifiedDateTime)} +
+
+ ) +} + +const FolderGridLayout = ({ + path, + folderChildren, + selected, + toggleItemSelected, + totalSelected, + toggleTotalSelected, + totalGenerating, + handleSelectedDownload, + folderGenerating, + handleSelectedPermalink, + handleFolderDownload, + toast, +}) => { + const clipboard = useClipboard() + const hashedToken = getStoredToken(path) + + const { t } = useTranslation() + + // Get item path from item name + const getItemPath = (name: string) => `${path === '/' ? '' : path}/${encodeURIComponent(name)}` + + return ( +
+
+
{t('{{count}} item(s)', { count: folderChildren.length })}
+
+ + + {totalGenerating ? ( + + ) : ( + + )} +
+
+ +
+ {folderChildren.map((c: OdFolderChildren) => ( +
+
+ {c.folder ? ( +
+ { + clipboard.copy(`${getBaseUrl()}${getItemPath(c.name)}`) + toast(t('Copied folder permalink.'), { icon: '👌' }) + }} + > + + + {folderGenerating[c.id] ? ( + + ) : ( + + + + )} +
+ ) : ( +
+ { + clipboard.copy( + `${getBaseUrl()}/api/raw/?path=${getItemPath(c.name)}${ + hashedToken ? `&odpt=${hashedToken}` : '' + }` + ) + toast.success(t('Copied raw file permalink.')) + }} + > + + + + + +
+ )} +
+ +
+ {!c.folder && !(c.name === '.password') && ( + toggleItemSelected(c.id)} + title={t('Select file')} + /> + )} +
+ + + + +
+ ))} +
+
+ ) +} + +export default FolderGridLayout diff --git a/src/components/FolderListLayout.tsx b/src/components/FolderListLayout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..980b9803c7ad4cc3b4fa12c9070dcde28ff057d2 --- /dev/null +++ b/src/components/FolderListLayout.tsx @@ -0,0 +1,184 @@ +import type { OdFolderChildren } from '../types' + +import Link from 'next/link' +import { FC } from 'react' +import { useClipboard } from 'use-clipboard-copy' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation } from 'next-i18next' + +import { getBaseUrl } from '../utils/getBaseUrl' +import { humanFileSize, formatModifiedDateTime } from '../utils/fileDetails' + +import { Downloading, Checkbox, ChildIcon, ChildName } from './FileListing' +import { getStoredToken } from '../utils/protectedRouteHandler' + +const FileListItem: FC<{ fileContent: OdFolderChildren }> = ({ fileContent: c }) => { + return ( +
+
+
+ +
+ +
+
+ {formatModifiedDateTime(c.lastModifiedDateTime)} +
+
+ {humanFileSize(c.size)} +
+
+ ) +} + +const FolderListLayout = ({ + path, + folderChildren, + selected, + toggleItemSelected, + totalSelected, + toggleTotalSelected, + totalGenerating, + handleSelectedDownload, + folderGenerating, + handleSelectedPermalink, + handleFolderDownload, + toast, +}) => { + const clipboard = useClipboard() + const hashedToken = getStoredToken(path) + + const { t } = useTranslation() + + // Get item path from item name + const getItemPath = (name: string) => `${path === '/' ? '' : path}/${encodeURIComponent(name)}` + + return ( +
+
+
+ {t('Name')} +
+
+ {t('Last Modified')} +
+
+ {t('Size')} +
+
+ {t('Actions')} +
+
+
+ + + {totalGenerating ? ( + + ) : ( + + )} +
+
+
+ + {folderChildren.map((c: OdFolderChildren) => ( +
+ + + + + {c.folder ? ( +
+ { + clipboard.copy(`${getBaseUrl()}${`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`}`) + toast(t('Copied folder permalink.'), { icon: '👌' }) + }} + > + + + {folderGenerating[c.id] ? ( + + ) : ( + { + const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}` + handleFolderDownload(p, c.id, c.name)() + }} + > + + + )} +
+ ) : ( +
+ { + clipboard.copy( + `${getBaseUrl()}/api/raw/?path=${getItemPath(c.name)}${hashedToken ? `&odpt=${hashedToken}` : ''}` + ) + toast.success(t('Copied raw file permalink.')) + }} + > + + + + + +
+ )} +
+ {!c.folder && !(c.name === '.password') && ( + toggleItemSelected(c.id)} + title={t('Select file')} + /> + )} +
+
+ ))} +
+ ) +} + +export default FolderListLayout diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5e39c671098e266c1dea082779998e3770959e7b --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,18 @@ +import config from '../../config/site.config' + +const createFooterMarkup = () => { + return { + __html: config.footer, + } +} + +const Footer = () => { + return ( +
+ ) +} + +export default Footer diff --git a/src/components/FourOhFour.tsx b/src/components/FourOhFour.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a66460889527e2c6b4ddc9510d0bf2d90fe77b2e --- /dev/null +++ b/src/components/FourOhFour.tsx @@ -0,0 +1,43 @@ +import Image from 'next/image' +import { Trans } from 'next-i18next' + +const FourOhFour: React.FC<{ errorMsg: string }> = ({ errorMsg }) => { + return ( +
+
+ 404 +
+
+
+ + {/* eslint-disable-next-line react/no-unescaped-entities */} + Oops, that's a four-oh-four. + +
+
+ {errorMsg} +
+
+ + Press{' '} + + F12 + {' '} + and open devtools for more details, or seek help at{' '} + + onedrive-vercel-index discussions + + . + +
+
+
+ ) +} + +export default FourOhFour diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4589467421d6375864cbf7052ba9f7e7d80d4686 --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,24 @@ +const Loading: React.FC<{ loadingText: string }> = ({ loadingText }) => { + return ( +
+ +
{loadingText}
+
+ ) +} + +// As there is no CSS-in-JS styling system, pass class list to override styles +export const LoadingIcon: React.FC<{ className?: string }> = ({ className }) => { + return ( + + + + + ) +} + +export default Loading diff --git a/src/components/MultiFileDownloader.tsx b/src/components/MultiFileDownloader.tsx new file mode 100644 index 0000000000000000000000000000000000000000..69f7445b0354966e5da43a0ca0e8048677617b53 --- /dev/null +++ b/src/components/MultiFileDownloader.tsx @@ -0,0 +1,259 @@ +import { NextRouter } from 'next/router' +import toast from 'react-hot-toast' +import JSZip from 'jszip' +import { useTranslation } from 'next-i18next' + +import { fetcher } from '../utils/fetchWithSWR' +import { getStoredToken } from '../utils/protectedRouteHandler' + +/** + * A loading toast component with file download progress support + * @param props + * @param props.router Next router instance, used for reloading the page + * @param props.progress Current downloading and compression progress (returned by jszip metadata) + */ +export function DownloadingToast({ router, progress }: { router: NextRouter; progress?: string }) { + const { t } = useTranslation() + + return ( +
+
+ {progress ? t('Downloading {{progress}}%', { progress }) : t('Downloading selected files...')} + +
+
+
+
+
+
+ +
+ ) +} + +// Blob download helper +export function downloadBlob({ blob, name }: { blob: Blob; name: string }) { + // Prepare for download + const el = document.createElement('a') + el.style.display = 'none' + document.body.appendChild(el) + + // Download zip file + const bUrl = window.URL.createObjectURL(blob) + el.href = bUrl + el.download = name + el.click() + window.URL.revokeObjectURL(bUrl) + el.remove() +} + +/** + * Download multiple files after compressing them into a zip + * @param toastId Toast ID to be used for toast notification + * @param files Files to be downloaded + * @param folder Optional folder name to hold files, otherwise flatten files in the zip + */ +export async function downloadMultipleFiles({ + toastId, + router, + files, + folder, +}: { + toastId: string + router: NextRouter + files: { name: string; url: string }[] + folder?: string +}): Promise { + const zip = new JSZip() + const dir = folder ? zip.folder(folder)! : zip + + // Add selected file blobs to zip + files.forEach(({ name, url }) => { + dir.file( + name, + fetch(url).then(r => { + return r.blob() + }) + ) + }) + + // Create zip file and download it + const b = await zip.generateAsync({ type: 'blob' }, metadata => { + toast.loading(, { + id: toastId, + }) + }) + downloadBlob({ blob: b, name: folder ? folder + '.zip' : 'download.zip' }) +} + +/** + * Download hierarchical tree-like files after compressing them into a zip + * @param toastId Toast ID to be used for toast notification + * @param files Files to be downloaded. Array of file and folder items excluding root folder. + * Folder items MUST be in front of its children items in the array. + * Use async generator because generation of the array may be slow. + * When waiting for its generation, we can meanwhile download bodies of already got items. + * Only folder items can have url undefined. + * @param basePath Root dir path of files to be downloaded + * @param folder Optional folder name to hold files, otherwise flatten files in the zip + */ +export async function downloadTreelikeMultipleFiles({ + toastId, + router, + files, + basePath, + folder, +}: { + toastId: string + router: NextRouter + files: AsyncGenerator<{ + name: string + url?: string + path: string + isFolder: boolean + }> + basePath: string + folder?: string +}): Promise { + const zip = new JSZip() + const root = folder ? zip.folder(folder)! : zip + const map = [{ path: basePath, dir: root }] + + // Add selected file blobs to zip according to its path + for await (const { name, url, path, isFolder } of files) { + // Search parent dir in map + const i = map + .slice() + .reverse() + .findIndex( + ({ path: parent }) => + path.substring(0, parent.length) === parent && path.substring(parent.length + 1).indexOf('/') === -1 + ) + if (i === -1) { + throw new Error('File array does not satisfy requirement') + } + + // Add file or folder to zip + const dir = map[map.length - 1 - i].dir + if (isFolder) { + map.push({ path, dir: dir.folder(name)! }) + } else { + dir.file( + name, + fetch(url!).then(r => r.blob()) + ) + } + } + + // Create zip file and download it + const b = await zip.generateAsync({ type: 'blob' }, metadata => { + toast.loading(, { + id: toastId, + }) + }) + downloadBlob({ blob: b, name: folder ? folder + '.zip' : 'download.zip' }) +} + +interface TraverseItem { + path: string + meta: any + isFolder: boolean + error?: { status: number; message: string } +} + +/** + * One-shot concurrent top-down file traversing for the folder. + * Due to react hook limit, we cannot reuse SWR utils for recursive actions. + * We will directly fetch API and arrange responses instead. + * In folder tree, we visit folders top-down as concurrently as possible. + * Every time we visit a folder, we fetch and return meta of all its children. + * If folders have pagination, partically retrieved items are not returned immediately, + * but after all children of the folder have been successfully retrieved. + * If an error occurred in paginated fetching, all children will be dropped. + * @param path Folder to be traversed. The path should be cleaned in advance. + * @returns Array of items representing folders and files of traversed folder top-down and excluding root folder. + * Due to top-down, Folder items are ALWAYS in front of its children items. + * Error key in the item will contain the error when there is a handleable error. + */ +export async function* traverseFolder(path: string): AsyncGenerator { + const hashedToken = getStoredToken(path) + + // Generate the task passed to Promise.race to request a folder + const genTask = async (i: number, path: string, next?: string) => { + return { + i, + path, + data: await fetcher([ + next ? `/api/?path=${path}&next=${next}` : `/api?path=${path}`, + hashedToken ?? undefined, + ]).catch(error => ({ i, path, error })), + } + } + + // Pool containing Promises of folder requests + let pool = [genTask(0, path)] + + // Map as item buffer for folders with pagination + const buf: { [k: string]: TraverseItem[] } = {} + + // filter(() => true) removes gaps in the array + while (pool.filter(() => true).length > 0) { + let info: { i: number; path: string; data: any } + try { + info = await Promise.race(pool.filter(() => true)) + } catch (error: any) { + const { i, path, error: innerError } = error + // 4xx errors are identified as handleable errors + if (Math.floor(innerError.status / 100) === 4) { + delete pool[i] + yield { + path, + meta: {}, + isFolder: true, + error: { status: innerError.status, message: innerError.message.error }, + } + continue + } else { + throw error + } + } + + const { i, path, data } = info + if (!data || !data.folder) { + throw new Error('Path is not folder') + } + delete pool[i] + + const items = data.folder.value.map((c: any) => { + const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}` + return { path: p, meta: c, isFolder: Boolean(c.folder) } + }) as TraverseItem[] + + if (data.next) { + buf[path] = (buf[path] ?? []).concat(items) + + // Append next page task to the pool at the end + const i = pool.length + pool[i] = genTask(i, path, data.next) + } else { + const allItems = (buf[path] ?? []).concat(items) + if (buf[path]) { + delete buf[path] + } + + allItems + .filter(item => item.isFolder) + .forEach(item => { + // Append new folder tasks to the pool at the end + const i = pool.length + pool[i] = genTask(i, item.path) + }) + yield* allItems + } + } +} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9a7ac24aae843c166fad0700f9ff6bb7e2746d95 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,203 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { IconName } from '@fortawesome/fontawesome-svg-core' +import { Dialog, Transition } from '@headlessui/react' +import toast, { Toaster } from 'react-hot-toast' +import { useHotkeys } from 'react-hotkeys-hook' + +import Link from 'next/link' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { Fragment, useEffect, useState } from 'react' +import { useTranslation } from 'next-i18next' + +import siteConfig from '../../config/site.config' +import SearchModal from './SearchModal' +import SwitchLang from './SwitchLang' +import useDeviceOS from '../utils/useDeviceOS' + +const Navbar = () => { + const router = useRouter() + const os = useDeviceOS() + + const [tokenPresent, setTokenPresent] = useState(false) + const [isOpen, setIsOpen] = useState(false) + + const [searchOpen, setSearchOpen] = useState(false) + const openSearchBox = () => setSearchOpen(true) + + useHotkeys(`${os === 'mac' ? 'meta' : 'ctrl'}+k`, e => { + openSearchBox() + e.preventDefault() + }) + + useEffect(() => { + const storedToken = () => { + for (const r of siteConfig.protectedRoutes) { + if (localStorage.hasOwnProperty(r)) { + return true + } + } + return false + } + setTokenPresent(storedToken()) + }, []) + + const { t } = useTranslation() + + const clearTokens = () => { + setIsOpen(false) + + siteConfig.protectedRoutes.forEach(r => { + localStorage.removeItem(r) + }) + + toast.success(t('Cleared all tokens')) + setTimeout(() => { + router.reload() + }, 1000) + } + + return ( +
+ + + + +
+ + icon + {siteConfig.title} + + +
+ + + + + {siteConfig.links.length !== 0 && + siteConfig.links.map((l: { name: string; link: string }) => ( + + + + { + // Append link name comments here to add translations + // t('Weibo') + t(l.name) + } + + + ))} + + {siteConfig.email && ( + + + {t('Email')} + + )} + + {tokenPresent && ( + + )} +
+
+ + + setIsOpen(false)}> +
+ + + + + {/* This element is to trick the browser into centering the modal contents. */} + + +
+ + {t('Clear all tokens?')} + +
+

+ {t('These tokens are used to authenticate yourself into password protected folders, ') + + t('clearing them means that you will need to re-enter the passwords again.')} +

+
+ +
+ {siteConfig.protectedRoutes.map((r, i) => ( +
+ + {r} +
+ ))} +
+ +
+ + +
+
+
+
+
+
+
+ ) +} + +export default Navbar diff --git a/src/components/SearchModal.tsx b/src/components/SearchModal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2b51a95b61493c61e9f731f277763627c0a09fa2 --- /dev/null +++ b/src/components/SearchModal.tsx @@ -0,0 +1,261 @@ +import axios from 'axios' +import useSWR, { SWRResponse } from 'swr' +import { Dispatch, Fragment, SetStateAction, useState } from 'react' +import AwesomeDebouncePromise from 'awesome-debounce-promise' +import { useAsync } from 'react-async-hook' +import useConstant from 'use-constant' +import { useTranslation } from 'next-i18next' + +import Link from 'next/link' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Dialog, Transition } from '@headlessui/react' + +import type { OdDriveItem, OdSearchResult } from '../types' +import { LoadingIcon } from './Loading' + +import { getFileIcon } from '../utils/getFileIcon' +import { fetcher } from '../utils/fetchWithSWR' +import siteConfig from '../../config/site.config' + +/** + * Extract the searched item's path in field 'parentReference' and convert it to the + * absolute path represented in onedrive-vercel-index + * + * @param path Path returned from the parentReference field of the driveItem + * @returns The absolute path of the driveItem in the search result + */ +function mapAbsolutePath(path: string): string { + // path is in the format of '/drive/root:/path/to/file', if baseDirectory is '/' then we split on 'root:', + // otherwise we split on the user defined 'baseDirectory' + const absolutePath = path.split(siteConfig.baseDirectory === '/' ? 'root:' : siteConfig.baseDirectory) + // path returned by the API may contain #, by doing a decodeURIComponent and then encodeURIComponent we can + // replace URL sensitive characters such as the # with %23 + return absolutePath.length > 1 // solve https://github.com/spencerwooo/onedrive-vercel-index/issues/539 + ? absolutePath[1] + .split('/') + .map(p => encodeURIComponent(decodeURIComponent(p))) + .join('/') + : '' +} + +/** + * Implements a debounced search function that returns a promise that resolves to an array of + * search results. + * + * @returns A react hook for a debounced async search of the drive + */ +function useDriveItemSearch() { + const [query, setQuery] = useState('') + const searchDriveItem = async (q: string) => { + const { data } = await axios.get(`/api/search/?q=${q}`) + + // Map parentReference to the absolute path of the search result + data.map(item => { + item['path'] = + 'path' in item.parentReference + ? // OneDrive International have the path returned in the parentReference field + `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}` + : // OneDrive for Business/Education does not, so we need extra steps here + '' + }) + + return data + } + + const debouncedDriveItemSearch = useConstant(() => AwesomeDebouncePromise(searchDriveItem, 1000)) + const results = useAsync(async () => { + if (query.length === 0) { + return [] + } else { + return debouncedDriveItemSearch(query) + } + }, [query]) + + return { + query, + setQuery, + results, + } +} + +function SearchResultItemTemplate({ + driveItem, + driveItemPath, + itemDescription, + disabled, +}: { + driveItem: OdSearchResult[number] + driveItemPath: string + itemDescription: string + disabled: boolean +}) { + return ( + + +
+
{driveItem.name}
+
+ {itemDescription} +
+
+ + ) +} + +function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) { + const { data, error }: SWRResponse = useSWR( + [`/api/item/?id=${result.id}`], + fetcher + ) + + const { t } = useTranslation() + + if (error) { + return ( + + ) + } + if (!data) { + return ( + + ) + } + + const driveItemPath = `${mapAbsolutePath(data.parentReference.path)}/${encodeURIComponent(data.name)}` + return ( + + ) +} + +function SearchResultItem({ result }: { result: OdSearchResult[number] }) { + if (result.path === '') { + // path is empty, which means we need to fetch the parentReference to get the path + return + } else { + // path is not an empty string in the search result, such that we can directly render the component as is + const driveItemPath = decodeURIComponent(result.path) + return ( + + ) + } +} + +export default function SearchModal({ + searchOpen, + setSearchOpen, +}: { + searchOpen: boolean + setSearchOpen: Dispatch> +}) { + const { query, setQuery, results } = useDriveItemSearch() + + const { t } = useTranslation() + + const closeSearchBox = () => { + setSearchOpen(false) + setQuery('') + } + + return ( + + +
+ + + + + +
+ + + setQuery(e.target.value)} + /> +
ESC
+
+
+ {results.loading && ( +
+ + {t('Loading ...')} +
+ )} + {results.error && ( +
+ {t('Error: {{message}}', { message: results.error.message })} +
+ )} + {results.result && ( + <> + {results.result.length === 0 ? ( +
{t('Nothing here.')}
+ ) : ( + results.result.map(result => ) + )} + + )} +
+
+
+
+
+
+ ) +} diff --git a/src/components/SwitchLang.tsx b/src/components/SwitchLang.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2c472f8831212c142b254b0a2223131f40481b26 --- /dev/null +++ b/src/components/SwitchLang.tsx @@ -0,0 +1,86 @@ +import { Fragment } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Menu, Transition } from '@headlessui/react' + +import { useRouter } from 'next/router' +import Link from 'next/link' +import { useCookies, withCookies } from 'react-cookie' + +// https://headlessui.dev/react/menu#integrating-with-next-js +const CustomLink = ({ href, children, as, locale, ...props }): JSX.Element => { + return ( + + {children} + + ) +} + +const localeText = (locale: string): string => { + switch (locale) { + case 'de-DE': + return '🇩🇪 Deutsch' + case 'en': + return '🇬🇧 English' + case 'es': + return '🇪🇸 Español' + case 'zh-CN': + return '🇨🇳 简体中文' + case 'hi': + return '🇮🇳 हिन्दी' + case 'id': + return '🇮🇩 Indonesia' + case 'tr-TR': + return '🇹🇷 Türkçe' + case 'zh-TW': + return '🇹🇼 繁體中文' + default: + return '🇬🇧 English' + } +} + +const SwitchLang = () => { + const { locales, pathname, query, asPath } = useRouter() + + const [_, setCookie] = useCookies(['NEXT_LOCALE']) + + return ( +
+ + + + + + + + + {locales!.map(locale => ( + + setCookie('NEXT_LOCALE', locale, { path: '/' })} + > +
+ {localeText(locale)} +
+
+
+ ))} +
+
+
+
+ ) +} + +export default withCookies(SwitchLang) diff --git a/src/components/SwitchLayout.tsx b/src/components/SwitchLayout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..97579a886e8d0946c843b0b4ae9754f13deceeb9 --- /dev/null +++ b/src/components/SwitchLayout.tsx @@ -0,0 +1,79 @@ +import { Fragment } from 'react' +import { IconProp } from '@fortawesome/fontawesome-svg-core' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Listbox, Transition } from '@headlessui/react' +import { useTranslation } from 'next-i18next' + +import useLocalStorage from '../utils/useLocalStorage' + +export const layouts: Array<{ id: number; name: 'Grid' | 'List'; icon: IconProp }> = [ + { id: 1, name: 'List', icon: 'th-list' }, + { id: 2, name: 'Grid', icon: 'th' }, +] + +const SwitchLayout = () => { + const [preferredLayout, setPreferredLayout] = useLocalStorage('preferredLayout', layouts[0]) + + const { t } = useTranslation() + + return ( +
+ + + + + + { + // t('Grid') + // t('List') + t(preferredLayout.name) + } + + + + + + + + + + {layouts.map(layout => ( + + + + { + // t('Grid') + // t('List') + t(layout.name) + } + + {layout.name === preferredLayout.name && ( + + + + )} + + ))} + + + +
+ ) +} + +export default SwitchLayout diff --git a/src/components/previews/AudioPreview.tsx b/src/components/previews/AudioPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..26beb62adbd2b2e24d1e3a8fcb6f5948ef0a313e --- /dev/null +++ b/src/components/previews/AudioPreview.tsx @@ -0,0 +1,116 @@ +import type { OdFileObject } from '../../types' +import { FC, useEffect, useRef, useState } from 'react' + +import ReactAudioPlayer from 'react-audio-player' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation } from 'next-i18next' +import { useRouter } from 'next/router' + +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer, PreviewContainer } from './Containers' +import { LoadingIcon } from '../Loading' +import { formatModifiedDateTime } from '../../utils/fileDetails' +import { getStoredToken } from '../../utils/protectedRouteHandler' + +enum PlayerState { + Loading, + Ready, + Playing, + Paused, +} + +const AudioPreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { t } = useTranslation() + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + const rapRef = useRef(null) + const [playerStatus, setPlayerStatus] = useState(PlayerState.Loading) + const [playerVolume, setPlayerVolume] = useState(1) + + // Render audio thumbnail, and also check for broken thumbnails + const thumbnail = `/api/thumbnail/?path=${asPath}&size=medium${hashedToken ? `&odpt=${hashedToken}` : ''}` + const [brokenThumbnail, setBrokenThumbnail] = useState(false) + + useEffect(() => { + // Manually get the HTML audio element and set onplaying event. + // - As the default event callbacks provided by the React component does not guarantee playing state to be set + // - properly when the user seeks through the timeline or the audio is buffered. + const rap = rapRef.current?.audioEl.current + if (rap) { + rap.oncanplay = () => setPlayerStatus(PlayerState.Ready) + rap.onended = () => setPlayerStatus(PlayerState.Paused) + rap.onpause = () => setPlayerStatus(PlayerState.Paused) + rap.onplay = () => setPlayerStatus(PlayerState.Playing) + rap.onplaying = () => setPlayerStatus(PlayerState.Playing) + rap.onseeking = () => setPlayerStatus(PlayerState.Loading) + rap.onwaiting = () => setPlayerStatus(PlayerState.Loading) + rap.onerror = () => setPlayerStatus(PlayerState.Paused) + rap.onvolumechange = () => setPlayerVolume(rap.volume) + } + }, []) + + return ( + <> + +
+
+
+ +
+ + {!brokenThumbnail ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {file.name} setBrokenThumbnail(true)} + /> +
+ ) : ( + + )} +
+ +
+
+
{file.name}
+
+ {t('Last modified:') + ' ' + formatModifiedDateTime(file.lastModifiedDateTime)} +
+
+ + +
+
+
+ + + + + + ) +} + +export default AudioPreview diff --git a/src/components/previews/CodePreview.tsx b/src/components/previews/CodePreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..980bd8fed86a4bb902a5fb1f5d4c346a253550cb --- /dev/null +++ b/src/components/previews/CodePreview.tsx @@ -0,0 +1,60 @@ +import { FC } from 'react' +import { useTranslation } from 'next-i18next' +import useSystemTheme from 'react-use-system-theme' +import { useRouter } from 'next/router' + +import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter' +import { tomorrowNightEighties, tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/hljs' + +import useFileContent from '../../utils/fetchOnMount' +import { getLanguageByFileName } from '../../utils/getPreviewType' +import FourOhFour from '../FourOhFour' +import Loading from '../Loading' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer, PreviewContainer } from './Containers' + +const CodePreview: FC<{ file: any }> = ({ file }) => { + const { asPath } = useRouter() + const { response: content, error, validating } = useFileContent(`/api/raw/?path=${asPath}`, asPath) + + const theme = useSystemTheme('dark') + const { t } = useTranslation() + + if (error) { + return ( + + + + ) + } + if (validating) { + return ( + <> + + + + + + + + ) + } + + return ( + <> + + + {content} + + + + + + + ) +} + +export default CodePreview diff --git a/src/components/previews/Containers.tsx b/src/components/previews/Containers.tsx new file mode 100644 index 0000000000000000000000000000000000000000..757cb1671df25d3ecbde93e90fb235a536194763 --- /dev/null +++ b/src/components/previews/Containers.tsx @@ -0,0 +1,11 @@ +export function PreviewContainer({ children }): JSX.Element { + return
{children}
+} + +export function DownloadBtnContainer({ children }): JSX.Element { + return ( +
+ {children} +
+ ) +} diff --git a/src/components/previews/DefaultPreview.tsx b/src/components/previews/DefaultPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ac7a6d366c1419281f41d6eaf26df6a6f2cffd2b --- /dev/null +++ b/src/components/previews/DefaultPreview.tsx @@ -0,0 +1,82 @@ +import type { OdFileObject } from '../../types' +import { FC } from 'react' + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation } from 'next-i18next' + +import { getFileIcon } from '../../utils/getFileIcon' +import { formatModifiedDateTime, humanFileSize } from '../../utils/fileDetails' + +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer, PreviewContainer } from './Containers' + +const DefaultPreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { t } = useTranslation() + + return ( +
+ +
+
+ +
{file.name}
+
+ +
+
+
{t('Last modified')}
+
{formatModifiedDateTime(file.lastModifiedDateTime)}
+
+ +
+
{t('File size')}
+
{humanFileSize(file.size)}
+
+ +
+
{t('MIME type')}
+
{file.file?.mimeType ?? t('Unavailable')}
+
+ +
+
{t('Hashes')}
+ + + + + + + + + + + + + + + +
+ Quick XOR + + {file.file.hashes?.quickXorHash ?? t('Unavailable')} +
+ SHA1 + + {file.file.hashes?.sha1Hash ?? t('Unavailable')} +
+ SHA256 + + {file.file.hashes?.sha256Hash ?? t('Unavailable')} +
+
+
+
+
+ + + +
+ ) +} + +export default DefaultPreview diff --git a/src/components/previews/EPUBPreview.tsx b/src/components/previews/EPUBPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..82d4e6c505d6629ec88c437a7dcdb3d00056eac7 --- /dev/null +++ b/src/components/previews/EPUBPreview.tsx @@ -0,0 +1,77 @@ +import type { OdFileObject } from '../../types' + +import { FC, useEffect, useRef, useState } from 'react' +import { ReactReader } from 'react-reader' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import Loading from '../Loading' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer } from './Containers' +import { getStoredToken } from '../../utils/protectedRouteHandler' + +const EPUBPreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + const [epubContainerWidth, setEpubContainerWidth] = useState(400) + const epubContainer = useRef(null) + + useEffect(() => { + setEpubContainerWidth(epubContainer.current ? epubContainer.current.offsetWidth : 400) + }, []) + + const [location, setLocation] = useState() + const onLocationChange = (cfiStr: string) => setLocation(cfiStr) + + const { t } = useTranslation() + + // Fix for not valid epub files according to + // https://github.com/gerhardsletten/react-reader/issues/33#issuecomment-673964947 + const fixEpub = rendition => { + const spineGet = rendition.book.spine.get.bind(rendition.book.spine) + rendition.book.spine.get = function (target: string) { + const targetStr = target as string + let t = spineGet(target) + while (t == null && targetStr.startsWith('../')) { + target = targetStr.substring(3) + t = spineGet(target) + } + return t + } + } + + return ( +
+
+
+
+ fixEpub(rendition)} + loadingView={} + location={location} + locationChanged={onLocationChange} + epubInitOptions={{ openAs: 'epub' }} + epubOptions={{ flow: 'scrolled', allowPopups: true }} + /> +
+
+
+ + + +
+ ) +} + +export default EPUBPreview diff --git a/src/components/previews/ImagePreview.tsx b/src/components/previews/ImagePreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..00e2a66cf54c9fbbfc30a8c423eeef098ec2f236 --- /dev/null +++ b/src/components/previews/ImagePreview.tsx @@ -0,0 +1,33 @@ +import type { OdFileObject } from '../../types' + +import { FC } from 'react' +import { useRouter } from 'next/router' + +import { PreviewContainer, DownloadBtnContainer } from './Containers' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { getStoredToken } from '../../utils/protectedRouteHandler' + +const ImagePreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + return ( + <> + + {/* eslint-disable-next-line @next/next/no-img-element */} + {file.name} + + + + + + ) +} + +export default ImagePreview diff --git a/src/components/previews/MarkdownPreview.tsx b/src/components/previews/MarkdownPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..df5ecd894c781450cd7ccca8ea930e453861e007 --- /dev/null +++ b/src/components/previews/MarkdownPreview.tsx @@ -0,0 +1,140 @@ +import { FC, CSSProperties, ReactNode } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import remarkMath from 'remark-math' +import rehypeKatex from 'rehype-katex' +import rehypeRaw from 'rehype-raw' +import { useTranslation } from 'next-i18next' +import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter' +import { tomorrowNight } from 'react-syntax-highlighter/dist/cjs/styles/hljs' + +import 'katex/dist/katex.min.css' + +import useFileContent from '../../utils/fetchOnMount' +import FourOhFour from '../FourOhFour' +import Loading from '../Loading' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer, PreviewContainer } from './Containers' + +const MarkdownPreview: FC<{ + file: any + path: string + standalone?: boolean +}> = ({ file, path, standalone = true }) => { + // The parent folder of the markdown file, which is also the relative image folder + const parentPath = standalone ? path.substring(0, path.lastIndexOf('/')) : path + + const { response: content, error, validating } = useFileContent(`/api/raw/?path=${parentPath}/${file.name}`, path) + const { t } = useTranslation() + + // Check if the image is relative path instead of a absolute url + const isUrlAbsolute = (url: string | string[]) => url.indexOf('://') > 0 || url.indexOf('//') === 0 + // Custom renderer: + const customRenderer = { + // img: to render images in markdown with relative file paths + img: ({ + alt, + src, + title, + width, + height, + style, + }: { + alt?: string + src?: string + title?: string + width?: string | number + height?: string | number + style?: CSSProperties + }) => { + return ( + // eslint-disable-next-line @next/next/no-img-element + {alt} + ) + }, + // code: to render code blocks with react-syntax-highlighter + code({ + className, + children, + inline, + ...props + }: { + className?: string | undefined + children: ReactNode + inline?: boolean + }) { + if (inline) { + return ( + + {children} + + ) + } + + const match = /language-(\w+)/.exec(className || '') + return ( + + {String(children).replace(/\n$/, '')} + + ) + }, + } + + if (error) { + return ( + + + + ) + } + if (validating) { + return ( + <> + + + + {standalone && ( + + + + )} + + ) + } + + return ( +
+ +
+ {/* Using rehypeRaw to render HTML inside Markdown is potentially dangerous, use under safe environments. (#18) */} + + {content} + +
+
+ {standalone && ( + + + + )} +
+ ) +} + +export default MarkdownPreview diff --git a/src/components/previews/OfficePreview.tsx b/src/components/previews/OfficePreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ce3df7959b2df25d8d932852b56fcdf7dcc0131c --- /dev/null +++ b/src/components/previews/OfficePreview.tsx @@ -0,0 +1,39 @@ +import type { OdFileObject } from '../../types' +import { FC, useEffect, useRef, useState } from 'react' +import { useRouter } from 'next/router' + +import Preview from 'preview-office-docs' + +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer } from './Containers' +import { getBaseUrl } from '../../utils/getBaseUrl' +import { getStoredToken } from '../../utils/protectedRouteHandler' + +const OfficePreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + const docContainer = useRef(null) + const [docContainerWidth, setDocContainerWidth] = useState(600) + + const docUrl = encodeURIComponent( + `${getBaseUrl()}/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}` + ) + + useEffect(() => { + setDocContainerWidth(docContainer.current ? docContainer.current.offsetWidth : 600) + }, []) + + return ( +
+
+ +
+ + + +
+ ) +} + +export default OfficePreview diff --git a/src/components/previews/PDFPreview.tsx b/src/components/previews/PDFPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1849ee21cfaa61e40a2b7c6d184ceacfba30b6e2 --- /dev/null +++ b/src/components/previews/PDFPreview.tsx @@ -0,0 +1,28 @@ +import { useRouter } from 'next/router' +import { getBaseUrl } from '../../utils/getBaseUrl' +import { getStoredToken } from '../../utils/protectedRouteHandler' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import { DownloadBtnContainer } from './Containers' + +const PDFEmbedPreview: React.FC<{ file: any }> = ({ file }) => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + + const pdfPath = encodeURIComponent( + `${getBaseUrl()}/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}` + ) + const url = `https://mozilla.github.io/pdf.js/web/viewer.html?file=${pdfPath}` + + return ( +
+
+ +
+ + + +
+ ) +} + +export default PDFEmbedPreview diff --git a/src/components/previews/TextPreview.tsx b/src/components/previews/TextPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c3e4b4664346b6f0f02742f3ab37db9514ec7a4a --- /dev/null +++ b/src/components/previews/TextPreview.tsx @@ -0,0 +1,61 @@ +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import FourOhFour from '../FourOhFour' +import Loading from '../Loading' +import DownloadButtonGroup from '../DownloadBtnGtoup' +import useFileContent from '../../utils/fetchOnMount' +import { DownloadBtnContainer, PreviewContainer } from './Containers' + +const TextPreview = ({ file }) => { + const { asPath } = useRouter() + const { t } = useTranslation() + + const { response: content, error, validating } = useFileContent(`/api/raw/?path=${asPath}`, asPath) + if (error) { + return ( + + + + ) + } + + if (validating) { + return ( + <> + + + + + + + + ) + } + + if (!content) { + return ( + <> + + + + + + + + ) + } + + return ( +
+ +
{content}
+
+ + + +
+ ) +} + +export default TextPreview diff --git a/src/components/previews/URLPreview.tsx b/src/components/previews/URLPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..06623ff9e0272d86e5541bd0979de756349a6744 --- /dev/null +++ b/src/components/previews/URLPreview.tsx @@ -0,0 +1,66 @@ +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import FourOhFour from '../FourOhFour' +import Loading from '../Loading' +import { DownloadButton } from '../DownloadBtnGtoup' +import useFileContent from '../../utils/fetchOnMount' +import { DownloadBtnContainer, PreviewContainer } from './Containers' + +const parseDotUrl = (content: string): string | undefined => { + return content + .split('\n') + .find(line => line.startsWith('URL=')) + ?.split('=')[1] +} + +const TextPreview = ({ file }) => { + const { asPath } = useRouter() + const { t } = useTranslation() + + const { response: content, error, validating } = useFileContent(`/api/raw/?path=${asPath}`, asPath) + if (error) { + return ( + + + + ) + } + + if (validating) { + return ( + + + + ) + } + + if (!content) { + return ( + + + + ) + } + + return ( +
+ +
{content}
+
+ +
+ window.open(parseDotUrl(content) ?? '')} + btnColor="blue" + btnText={t('Open URL')} + btnIcon="external-link-alt" + btnTitle={t('Open URL{{url}}', { url: ' ' + parseDotUrl(content) ?? '' })} + /> +
+
+
+ ) +} + +export default TextPreview diff --git a/src/components/previews/VideoPreview.tsx b/src/components/previews/VideoPreview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e700eadd89444a67871952816b8a326c3b7e419 --- /dev/null +++ b/src/components/previews/VideoPreview.tsx @@ -0,0 +1,178 @@ +import type { OdFileObject } from '../../types' + +import { FC, useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' + +import axios from 'axios' +import toast from 'react-hot-toast' +import Plyr from 'plyr-react' +import { useAsync } from 'react-async-hook' +import { useClipboard } from 'use-clipboard-copy' + +import { getBaseUrl } from '../../utils/getBaseUrl' +import { getExtension } from '../../utils/getFileIcon' +import { getStoredToken } from '../../utils/protectedRouteHandler' + +import { DownloadButton } from '../DownloadBtnGtoup' +import { DownloadBtnContainer, PreviewContainer } from './Containers' +import FourOhFour from '../FourOhFour' +import Loading from '../Loading' +import CustomEmbedLinkMenu from '../CustomEmbedLinkMenu' + +import 'plyr-react/plyr.css' + +const VideoPlayer: FC<{ + videoName: string + videoUrl: string + width?: number + height?: number + thumbnail: string + subtitle: string + isFlv: boolean + mpegts: any +}> = ({ videoName, videoUrl, width, height, thumbnail, subtitle, isFlv, mpegts }) => { + useEffect(() => { + // Really really hacky way to inject subtitles as file blobs into the video element + axios + .get(subtitle, { responseType: 'blob' }) + .then(resp => { + const track = document.querySelector('track') + track?.setAttribute('src', URL.createObjectURL(resp.data)) + }) + .catch(() => { + console.log('Could not load subtitle.') + }) + + if (isFlv) { + const loadFlv = () => { + // Really hacky way to get the exposed video element from Plyr + const video = document.getElementById('plyr') + const flv = mpegts.createPlayer({ url: videoUrl, type: 'flv' }) + flv.attachMediaElement(video) + flv.load() + } + loadFlv() + } + }, [videoUrl, isFlv, mpegts, subtitle]) + + // Common plyr configs, including the video source and plyr options + const plyrSource = { + type: 'video', + title: videoName, + poster: thumbnail, + tracks: [{ kind: 'captions', label: videoName, src: '', default: true }], + } + const plyrOptions: Plyr.Options = { + ratio: `${width ?? 16}:${height ?? 9}`, + fullscreen: { iosNative: true }, + } + if (!isFlv) { + // If the video is not in flv format, we can use the native plyr and add sources directly with the video URL + plyrSource['sources'] = [{ src: videoUrl }] + } + return +} + +const VideoPreview: FC<{ file: OdFileObject }> = ({ file }) => { + const { asPath } = useRouter() + const hashedToken = getStoredToken(asPath) + const clipboard = useClipboard() + + const [menuOpen, setMenuOpen] = useState(false) + const { t } = useTranslation() + + // OneDrive generates thumbnails for its video files, we pick the thumbnail with the highest resolution + const thumbnail = `/api/thumbnail/?path=${asPath}&size=large${hashedToken ? `&odpt=${hashedToken}` : ''}` + + // We assume subtitle files are beside the video with the same name, only webvtt '.vtt' files are supported + const vtt = `${asPath.substring(0, asPath.lastIndexOf('.'))}.vtt` + const subtitle = `/api/raw/?path=${vtt}${hashedToken ? `&odpt=${hashedToken}` : ''}` + + // We also format the raw video file for the in-browser player as well as all other players + const videoUrl = `/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}` + + const isFlv = getExtension(file.name) === 'flv' + const { + loading, + error, + result: mpegts, + } = useAsync(async () => { + if (isFlv) { + return (await import('mpegts.js')).default + } + }, [isFlv]) + + return ( + <> + + + {error ? ( + + ) : loading && isFlv ? ( + + ) : ( + + )} + + + +
+ window.open(videoUrl)} + btnColor="blue" + btnText={t('Download')} + btnIcon="file-download" + /> + { + clipboard.copy(`${getBaseUrl()}/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}`) + toast.success(t('Copied direct link to clipboard.')) + }} + btnColor="pink" + btnText={t('Copy direct link')} + btnIcon="copy" + /> + setMenuOpen(true)} + btnColor="teal" + btnText={t('Customise link')} + btnIcon="pen" + /> + + window.open(`iina://weblink?url=${getBaseUrl()}${videoUrl}`)} + btnText="IINA" + btnImage="/players/iina.png" + /> + window.open(`vlc://${getBaseUrl()}${videoUrl}`)} + btnText="VLC" + btnImage="/players/vlc.png" + /> + window.open(`potplayer://${getBaseUrl()}${videoUrl}`)} + btnText="PotPlayer" + btnImage="/players/potplayer.png" + /> + window.open(`nplayer-http://${window?.location.hostname ?? ''}${videoUrl}`)} + btnText="nPlayer" + btnImage="/players/nplayer.png" + /> +
+
+ + ) +} + +export default VideoPreview diff --git a/src/pages/[...path].tsx b/src/pages/[...path].tsx new file mode 100644 index 0000000000000000000000000000000000000000..0b8dcca8ddc19f7ef9148b19bcc91ef534c8bcf1 --- /dev/null +++ b/src/pages/[...path].tsx @@ -0,0 +1,43 @@ +import Head from 'next/head' +import { useRouter } from 'next/router' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import siteConfig from '../../config/site.config' +import Navbar from '../components/Navbar' +import FileListing from '../components/FileListing' +import Footer from '../components/Footer' +import Breadcrumb from '../components/Breadcrumb' +import SwitchLayout from '../components/SwitchLayout' + +export default function Folders() { + const { query } = useRouter() + + return ( +
+ + {siteConfig.title} + + +
+ +
+ + +
+
+ +
+
+ ) +} + +export async function getServerSideProps({ locale }) { + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + }, + } +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx new file mode 100644 index 0000000000000000000000000000000000000000..09ad03029dc39503bcd6485e127df0141a327fef --- /dev/null +++ b/src/pages/_app.tsx @@ -0,0 +1,132 @@ +import '@fortawesome/fontawesome-svg-core/styles.css' + +import '../styles/globals.css' +import '../styles/markdown-github.css' +import { Analytics } from '@vercel/analytics/react'; + +// Require had to be used to prevent SSR failure in Next.js +// Related discussion: https://github.com/FortAwesome/Font-Awesome/issues/19348 +const { library, config } = require('@fortawesome/fontawesome-svg-core') +config.autoAddCss = false + +import { + faFileImage, + faFilePdf, + faFileWord, + faFilePowerpoint, + faFileExcel, + faFileAudio, + faFileVideo, + faFileArchive, + faFileCode, + faFileAlt, + faFile, + faFolder, + faCopy, + faArrowAltCircleDown, + faTrashAlt, + faEnvelope, + faFlag, + faCheckCircle, +} from '@fortawesome/free-regular-svg-icons' +import { + faSearch, + faPen, + faCheck, + faPlus, + faMinus, + faCopy as faCopySolid, + faAngleRight, + faDownload, + faMusic, + faArrowLeft, + faArrowRight, + faFileDownload, + faUndo, + faBook, + faKey, + faSignOutAlt, + faCloud, + faChevronCircleDown, + faChevronDown, + faLink, + faExternalLinkAlt, + faExclamationCircle, + faExclamationTriangle, + faTh, + faThLarge, + faThList, + faHome, + faLanguage, +} from '@fortawesome/free-solid-svg-icons' +import * as Icons from '@fortawesome/free-brands-svg-icons' + +import type { AppProps } from 'next/app' +import NextNProgress from 'nextjs-progressbar' +import { appWithTranslation } from 'next-i18next' + +// import all brand icons with tree-shaking so all icons can be referenced in the app +const iconList = Object.keys(Icons) + .filter(k => k !== 'fab' && k !== 'prefix') + .map(icon => Icons[icon]) + +library.add( + faFileImage, + faFilePdf, + faFileWord, + faFilePowerpoint, + faFileExcel, + faFileAudio, + faFileVideo, + faFileArchive, + faFileCode, + faFileAlt, + faFile, + faFlag, + faFolder, + faMusic, + faArrowLeft, + faArrowRight, + faAngleRight, + faFileDownload, + faCopy, + faCopySolid, + faPlus, + faMinus, + faDownload, + faLink, + faUndo, + faBook, + faArrowAltCircleDown, + faKey, + faTrashAlt, + faSignOutAlt, + faEnvelope, + faCloud, + faChevronCircleDown, + faExternalLinkAlt, + faExclamationCircle, + faExclamationTriangle, + faHome, + faCheck, + faCheckCircle, + faSearch, + faChevronDown, + faTh, + faThLarge, + faThList, + faLanguage, + faPen, + ...iconList +) + +function MyApp({ Component, pageProps }: AppProps) { + return ( + <> + + + + + ) +} +export default appWithTranslation(MyApp) diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx new file mode 100644 index 0000000000000000000000000000000000000000..83d5bc302cdb64f2614c908d634affcdd155cc2f --- /dev/null +++ b/src/pages/_document.tsx @@ -0,0 +1,26 @@ +import Document, { Head, Html, Main, NextScript } from 'next/document' +import siteConfig from '../../config/site.config' + +class MyDocument extends Document { + render() { + return ( + + + + + + + {siteConfig.googleFontLinks.map(link => ( + + ))} + + +
+ + + + ) + } +} + +export default MyDocument diff --git a/src/pages/api/index.ts b/src/pages/api/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed80e7ac8be394053875d507ff8780a18c3b2b4a --- /dev/null +++ b/src/pages/api/index.ts @@ -0,0 +1,292 @@ +import { posix as pathPosix } from 'path' + +import type { NextApiRequest, NextApiResponse } from 'next' +import axios from 'axios' + +import apiConfig from '../../../config/api.config' +import siteConfig from '../../../config/site.config' +import { revealObfuscatedToken } from '../../utils/oAuthHandler' +import { compareHashedToken } from '../../utils/protectedRouteHandler' +import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore' +import { runCorsMiddleware } from './raw' + +const basePath = pathPosix.resolve('/', siteConfig.baseDirectory) +const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret) + +/** + * Encode the path of the file relative to the base directory + * + * @param path Relative path of the file to the base directory + * @returns Absolute path of the file inside OneDrive + */ +export function encodePath(path: string): string { + let encodedPath = pathPosix.join(basePath, path) + if (encodedPath === '/' || encodedPath === '') { + return '' + } + encodedPath = encodedPath.replace(/\/$/, '') + return `:${encodeURIComponent(encodedPath)}` +} + +/** + * Fetch the access token from Redis storage and check if the token requires a renew + * + * @returns Access token for OneDrive API + */ +export async function getAccessToken(): Promise { + const { accessToken, refreshToken } = await getOdAuthTokens() + + // Return in storage access token if it is still valid + if (typeof accessToken === 'string') { + console.log('Fetch access token from storage.') + return accessToken + } + + // Return empty string if no refresh token is stored, which requires the application to be re-authenticated + if (typeof refreshToken !== 'string') { + console.log('No refresh token, return empty access token.') + return '' + } + + // Fetch new access token with in storage refresh token + const body = new URLSearchParams() + body.append('client_id', apiConfig.clientId) + body.append('redirect_uri', apiConfig.redirectUri) + body.append('client_secret', clientSecret) + body.append('refresh_token', refreshToken) + body.append('grant_type', 'refresh_token') + + const resp = await axios.post(apiConfig.authApi, body, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }) + + if ('access_token' in resp.data && 'refresh_token' in resp.data) { + const { expires_in, access_token, refresh_token } = resp.data + await storeOdAuthTokens({ + accessToken: access_token, + accessTokenExpiry: parseInt(expires_in), + refreshToken: refresh_token, + }) + console.log('Fetch new access token with stored refresh token.') + return access_token + } + + return '' +} + +/** + * Match protected routes in site config to get path to required auth token + * @param path Path cleaned in advance + * @returns Path to required auth token. If not required, return empty string. + */ +export function getAuthTokenPath(path: string) { + // Ensure trailing slashes to compare paths component by component. Same for protectedRoutes. + // Since OneDrive ignores case, lower case before comparing. Same for protectedRoutes. + path = path.toLowerCase() + '/' + const protectedRoutes = siteConfig.protectedRoutes as string[] + let authTokenPath = '' + for (let r of protectedRoutes) { + if (typeof r !== 'string') continue + r = r.toLowerCase().replace(/\/$/, '') + '/' + if (path.startsWith(r)) { + authTokenPath = `${r}.password` + break + } + } + return authTokenPath +} + +/** + * Handles protected route authentication: + * - Match the cleanPath against an array of user defined protected routes + * - If a match is found: + * - 1. Download the .password file stored inside the protected route and parse its contents + * - 2. Check if the od-protected-token header is present in the request + * - The request is continued only if these two contents are exactly the same + * + * @param cleanPath Sanitised directory path, used for matching whether route is protected + * @param accessToken OneDrive API access token + * @param req Next.js request object + * @param res Next.js response object + */ +export async function checkAuthRoute( + cleanPath: string, + accessToken: string, + odTokenHeader: string +): Promise<{ code: 200 | 401 | 404 | 500; message: string }> { + // Handle authentication through .password + const authTokenPath = getAuthTokenPath(cleanPath) + + // Fetch password from remote file content + if (authTokenPath === '') { + return { code: 200, message: '' } + } + + try { + const token = await axios.get(`${apiConfig.driveApi}/root${encodePath(authTokenPath)}`, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: '@microsoft.graph.downloadUrl,file', + }, + }) + + // Handle request and check for header 'od-protected-token' + const odProtectedToken = await axios.get(token.data['@microsoft.graph.downloadUrl']) + // console.log(odTokenHeader, odProtectedToken.data.trim()) + + if ( + !compareHashedToken({ + odTokenHeader: odTokenHeader, + dotPassword: odProtectedToken.data.toString(), + }) + ) { + return { code: 401, message: 'Password required.' } + } + } catch (error: any) { + // Password file not found, fallback to 404 + if (error?.response?.status === 404) { + return { code: 404, message: "You didn't set a password." } + } else { + return { code: 500, message: 'Internal server error.' } + } + } + + return { code: 200, message: 'Authenticated.' } +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // If method is POST, then the API is called by the client to store acquired tokens + if (req.method === 'POST') { + const { obfuscatedAccessToken, accessTokenExpiry, obfuscatedRefreshToken } = req.body + const accessToken = revealObfuscatedToken(obfuscatedAccessToken) + const refreshToken = revealObfuscatedToken(obfuscatedRefreshToken) + + if (typeof accessToken !== 'string' || typeof refreshToken !== 'string') { + res.status(400).send('Invalid request body') + return + } + + await storeOdAuthTokens({ accessToken, accessTokenExpiry, refreshToken }) + res.status(200).send('OK') + return + } + + // If method is GET, then the API is a normal request to the OneDrive API for files or folders + const { path = '/', raw = false, next = '', sort = '' } = req.query + + // Set edge function caching for faster load times, check docs: + // https://vercel.com/docs/concepts/functions/edge-caching + res.setHeader('Cache-Control', apiConfig.cacheControlHeader) + + // Sometimes the path parameter is defaulted to '[...path]' which we need to handle + if (path === '[...path]') { + res.status(400).json({ error: 'No path specified.' }) + return + } + // If the path is not a valid path, return 400 + if (typeof path !== 'string') { + res.status(400).json({ error: 'Path query invalid.' }) + return + } + // Besides normalizing and making absolute, trailing slashes are trimmed + const cleanPath = pathPosix.resolve('/', pathPosix.normalize(path)).replace(/\/$/, '') + + // Validate sort param + if (typeof sort !== 'string') { + res.status(400).json({ error: 'Sort query invalid.' }) + return + } + + const accessToken = await getAccessToken() + + // Return error 403 if access_token is empty + if (!accessToken) { + res.status(403).json({ error: 'No access token.' }) + return + } + + // Handle protected routes authentication + const { code, message } = await checkAuthRoute(cleanPath, accessToken, req.headers['od-protected-token'] as string) + // Status code other than 200 means user has not authenticated yet + if (code !== 200) { + res.status(code).json({ error: message }) + return + } + // If message is empty, then the path is not protected. + // Conversely, protected routes are not allowed to serve from cache. + if (message !== '') { + res.setHeader('Cache-Control', 'no-cache') + } + + const requestPath = encodePath(cleanPath) + // Handle response from OneDrive API + const requestUrl = `${apiConfig.driveApi}/root${requestPath}` + // Whether path is root, which requires some special treatment + const isRoot = requestPath === '' + + // Go for file raw download link, add CORS headers, and redirect to @microsoft.graph.downloadUrl + // (kept here for backwards compatibility, and cache headers will be reverted to no-cache) + if (raw) { + await runCorsMiddleware(req, res) + res.setHeader('Cache-Control', 'no-cache') + + const { data } = await axios.get(requestUrl, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + // OneDrive international version fails when only selecting the downloadUrl (what a stupid bug) + select: 'id,@microsoft.graph.downloadUrl', + }, + }) + + if ('@microsoft.graph.downloadUrl' in data) { + res.redirect(data['@microsoft.graph.downloadUrl']) + } else { + res.status(404).json({ error: 'No download url found.' }) + } + return + } + + // Querying current path identity (file or folder) and follow up query childrens in folder + try { + const { data: identityData } = await axios.get(requestUrl, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: 'name,size,id,lastModifiedDateTime,folder,file,video,image', + }, + }) + + if ('folder' in identityData) { + const { data: folderData } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/children`, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + ...{ + select: 'name,size,id,lastModifiedDateTime,folder,file,video,image', + $top: siteConfig.maxItems, + }, + ...(next ? { $skipToken: next } : {}), + ...(sort ? { $orderby: sort } : {}), + }, + }) + + // Extract next page token from full @odata.nextLink + const nextPage = folderData['@odata.nextLink'] + ? folderData['@odata.nextLink'].match(/&\$skiptoken=(.+)/i)[1] + : null + + // Return paging token if specified + if (nextPage) { + res.status(200).json({ folder: folderData, next: nextPage }) + } else { + res.status(200).json({ folder: folderData }) + } + return + } + res.status(200).json({ file: identityData }) + return + } catch (error: any) { + res.status(error?.response?.code ?? 500).json({ error: error?.response?.data ?? 'Internal server error.' }) + return + } +} diff --git a/src/pages/api/item.ts b/src/pages/api/item.ts new file mode 100644 index 0000000000000000000000000000000000000000..f057288c3df3bf72533c26f191c819426cb80dfb --- /dev/null +++ b/src/pages/api/item.ts @@ -0,0 +1,36 @@ +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { getAccessToken } from '.' +import apiConfig from '../../../config/api.config' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Get access token from storage + const accessToken = await getAccessToken() + + // Get item details (specifically, its path) by its unique ID in OneDrive + const { id = '' } = req.query + + // Set edge function caching for faster load times, check docs: + // https://vercel.com/docs/concepts/functions/edge-caching + res.setHeader('Cache-Control', apiConfig.cacheControlHeader) + + if (typeof id === 'string') { + const itemApi = `${apiConfig.driveApi}/items/${id}` + + try { + const { data } = await axios.get(itemApi, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: 'id,name,parentReference', + }, + }) + res.status(200).json(data) + } catch (error: any) { + res.status(error?.response?.status ?? 500).json({ error: error?.response?.data ?? 'Internal server error.' }) + } + } else { + res.status(400).json({ error: 'Invalid driveItem ID.' }) + } + return +} diff --git a/src/pages/api/name/[name].ts b/src/pages/api/name/[name].ts new file mode 100644 index 0000000000000000000000000000000000000000..12eb12454514198e8812c17389f2d5f9c50e05cd --- /dev/null +++ b/src/pages/api/name/[name].ts @@ -0,0 +1,6 @@ +import type { NextApiRequest, NextApiResponse } from 'next' +import { default as rawFileHandler } from '../raw' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + rawFileHandler(req, res) +} diff --git a/src/pages/api/raw.ts b/src/pages/api/raw.ts new file mode 100644 index 0000000000000000000000000000000000000000..917eb81352d69c8aa0a0accf1f7ec893c99da869 --- /dev/null +++ b/src/pages/api/raw.ts @@ -0,0 +1,93 @@ +import { posix as pathPosix } from 'path' + +import type { NextApiRequest, NextApiResponse } from 'next' +import axios, { AxiosResponseHeaders } from 'axios' +import Cors from 'cors' + +import { driveApi, cacheControlHeader } from '../../../config/api.config' +import { encodePath, getAccessToken, checkAuthRoute } from '.' + +// CORS middleware for raw links: https://nextjs.org/docs/api-routes/api-middlewares +export function runCorsMiddleware(req: NextApiRequest, res: NextApiResponse) { + const cors = Cors({ methods: ['GET', 'HEAD'] }) + return new Promise((resolve, reject) => { + cors(req, res, result => { + if (result instanceof Error) { + return reject(result) + } + + return resolve(result) + }) + }) +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const accessToken = await getAccessToken() + if (!accessToken) { + res.status(403).json({ error: 'No access token.' }) + return + } + + const { path = '/', odpt = '', proxy = false } = req.query + + // Sometimes the path parameter is defaulted to '[...path]' which we need to handle + if (path === '[...path]') { + res.status(400).json({ error: 'No path specified.' }) + return + } + // If the path is not a valid path, return 400 + if (typeof path !== 'string') { + res.status(400).json({ error: 'Path query invalid.' }) + return + } + const cleanPath = pathPosix.resolve('/', pathPosix.normalize(path)) + + // Handle protected routes authentication + const odTokenHeader = (req.headers['od-protected-token'] as string) ?? odpt + + const { code, message } = await checkAuthRoute(cleanPath, accessToken, odTokenHeader) + // Status code other than 200 means user has not authenticated yet + if (code !== 200) { + res.status(code).json({ error: message }) + return + } + // If message is empty, then the path is not protected. + // Conversely, protected routes are not allowed to serve from cache. + if (message !== '') { + res.setHeader('Cache-Control', 'no-cache') + } + + await runCorsMiddleware(req, res) + try { + // Handle response from OneDrive API + const requestUrl = `${driveApi}/root${encodePath(cleanPath)}` + const { data } = await axios.get(requestUrl, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + // OneDrive international version fails when only selecting the downloadUrl (what a stupid bug) + select: 'id,size,@microsoft.graph.downloadUrl', + }, + }) + + if ('@microsoft.graph.downloadUrl' in data) { + // Only proxy raw file content response for files up to 4MB + if (proxy && 'size' in data && data['size'] < 4194304) { + const { headers, data: stream } = await axios.get(data['@microsoft.graph.downloadUrl'] as string, { + responseType: 'stream', + }) + headers['Cache-Control'] = cacheControlHeader + // Send data stream as response + res.writeHead(200, headers as AxiosResponseHeaders) + stream.pipe(res) + } else { + res.redirect(data['@microsoft.graph.downloadUrl']) + } + } else { + res.status(404).json({ error: 'No download url found.' }) + } + return + } catch (error: any) { + res.status(error?.response?.status ?? 500).json({ error: error?.response?.data ?? 'Internal server error.' }) + return + } +} diff --git a/src/pages/api/search.ts b/src/pages/api/search.ts new file mode 100644 index 0000000000000000000000000000000000000000..468ed59d0581255b16a72f0774e56202787dc5fa --- /dev/null +++ b/src/pages/api/search.ts @@ -0,0 +1,62 @@ +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { encodePath, getAccessToken } from '.' +import apiConfig from '../../../config/api.config' +import siteConfig from '../../../config/site.config' + +/** + * Sanitize the search query + * + * @param query User search query, which may contain special characters + * @returns Sanitised query string, which: + * - encodes the '<' and '>' characters, + * - replaces '?' and '/' characters with ' ', + * - replaces ''' with '''' + * Reference: https://stackoverflow.com/questions/41491222/single-quote-escaping-in-microsoft-graph. + */ +function sanitiseQuery(query: string): string { + const sanitisedQuery = query + .replace(/'/g, "''") + .replace('<', ' < ') + .replace('>', ' > ') + .replace('?', ' ') + .replace('/', ' ') + return encodeURIComponent(sanitisedQuery) +} + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Get access token from storage + const accessToken = await getAccessToken() + + // Query parameter from request + const { q: searchQuery = '' } = req.query + + // Set edge function caching for faster load times, check docs: + // https://vercel.com/docs/concepts/functions/edge-caching + res.setHeader('Cache-Control', apiConfig.cacheControlHeader) + + if (typeof searchQuery === 'string') { + // Construct Microsoft Graph Search API URL, and perform search only under the base directory + const searchRootPath = encodePath('/') + const encodedPath = searchRootPath === '' ? searchRootPath : searchRootPath + ':' + + const searchApi = `${apiConfig.driveApi}/root${encodedPath}/search(q='${sanitiseQuery(searchQuery)}')` + + try { + const { data } = await axios.get(searchApi, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: 'id,name,file,folder,parentReference', + top: siteConfig.maxItems, + }, + }) + res.status(200).json(data.value) + } catch (error: any) { + res.status(error?.response?.status ?? 500).json({ error: error?.response?.data ?? 'Internal server error.' }) + } + } else { + res.status(200).json([]) + } + return +} diff --git a/src/pages/api/thumbnail.ts b/src/pages/api/thumbnail.ts new file mode 100644 index 0000000000000000000000000000000000000000..69231fb219a955b96237a83c9bb954b43f23857a --- /dev/null +++ b/src/pages/api/thumbnail.ts @@ -0,0 +1,75 @@ +import type { OdThumbnail } from '../../types' + +import { posix as pathPosix } from 'path' + +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { checkAuthRoute, encodePath, getAccessToken } from '.' +import apiConfig from '../../../config/api.config' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const accessToken = await getAccessToken() + if (!accessToken) { + res.status(403).json({ error: 'No access token.' }) + return + } + + // Get item thumbnails by its path since we will later check if it is protected + const { path = '', size = 'medium', odpt = '' } = req.query + + // Set edge function caching for faster load times, if route is not protected, check docs: + // https://vercel.com/docs/concepts/functions/edge-caching + if (odpt === '') res.setHeader('Cache-Control', apiConfig.cacheControlHeader) + + // Check whether the size is valid - must be one of 'large', 'medium', or 'small' + if (size !== 'large' && size !== 'medium' && size !== 'small') { + res.status(400).json({ error: 'Invalid size' }) + return + } + // Sometimes the path parameter is defaulted to '[...path]' which we need to handle + if (path === '[...path]') { + res.status(400).json({ error: 'No path specified.' }) + return + } + // If the path is not a valid path, return 400 + if (typeof path !== 'string') { + res.status(400).json({ error: 'Path query invalid.' }) + return + } + const cleanPath = pathPosix.resolve('/', pathPosix.normalize(path)) + + const { code, message } = await checkAuthRoute(cleanPath, accessToken, odpt as string) + // Status code other than 200 means user has not authenticated yet + if (code !== 200) { + res.status(code).json({ error: message }) + return + } + // If message is empty, then the path is not protected. + // Conversely, protected routes are not allowed to serve from cache. + if (message !== '') { + res.setHeader('Cache-Control', 'no-cache') + } + + const requestPath = encodePath(cleanPath) + // Handle response from OneDrive API + const requestUrl = `${apiConfig.driveApi}/root${requestPath}` + // Whether path is root, which requires some special treatment + const isRoot = requestPath === '' + + try { + const { data } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/thumbnails`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }) + + const thumbnailUrl = data.value && data.value.length > 0 ? (data.value[0] as OdThumbnail)[size].url : null + if (thumbnailUrl) { + res.redirect(thumbnailUrl) + } else { + res.status(400).json({ error: "The item doesn't have a valid thumbnail." }) + } + } catch (error: any) { + res.status(error?.response?.status).json({ error: error?.response?.data ?? 'Internal server error.' }) + } + return +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5995424e9ea1a48f026ff0f25062adaf85b674ce --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,40 @@ +import Head from 'next/head' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import siteConfig from '../../config/site.config' +import Navbar from '../components/Navbar' +import FileListing from '../components/FileListing' +import Footer from '../components/Footer' +import Breadcrumb from '../components/Breadcrumb' +import SwitchLayout from '../components/SwitchLayout' + +export default function Home() { + return ( +
+ + {siteConfig.title} + + +
+ +
+ + +
+
+ +
+
+ ) +} + +export async function getServerSideProps({ locale }) { + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + }, + } +} diff --git a/src/pages/onedrive-vercel-index-oauth/step-1.tsx b/src/pages/onedrive-vercel-index-oauth/step-1.tsx new file mode 100644 index 0000000000000000000000000000000000000000..255478d96fb00383dd8248471690b741a8539906 --- /dev/null +++ b/src/pages/onedrive-vercel-index-oauth/step-1.tsx @@ -0,0 +1,156 @@ +import Head from 'next/head' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { useTranslation, Trans } from 'next-i18next' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import siteConfig from '../../../config/site.config' +import apiConfig from '../../../config/api.config' +import Navbar from '../../components/Navbar' +import Footer from '../../components/Footer' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +export default function OAuthStep1() { + const router = useRouter() + + const { t } = useTranslation() + + return ( +
+ + {t('OAuth Step 1 - {{title}}', { title: siteConfig.title })} + + +
+ + +
+
+
+ fabulous fireworks +
+

+ {t('Welcome to your new onedrive-vercel-index 🎉')} +

+ +

{t('Step 1/3: Preparations')}

+ +

+ + If you have not specified a REDIS_URL + inside your Vercel env variable, go initialise one at{' '} + + Upstash + + . Docs:{' '} + + Vercel Integration - Upstash + + . + +

+ +

+ + Authorisation is required as no valid{' '} + access_token or{' '} + refresh_token{' '} + is present on this deployed instance. Check the following configurations before proceeding with + authorising onedrive-vercel-index with your own Microsoft account. + +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ CLIENT_ID + + {apiConfig.clientId} +
+ CLIENT_SECRET* + + {apiConfig.obfuscatedClientSecret} +
+ REDIRECT_URI + + {apiConfig.redirectUri} +
+ Auth API URL + + {apiConfig.authApi} +
+ Drive API URL + + {apiConfig.driveApi} +
+ API Scope + + {apiConfig.scope} +
+
+ +

+ + If you see anything + missing or incorrect, you need to reconfigure{' '} + /config/api.config.js and redeploy this instance. + +

+ +
+ +
+
+
+
+ +
+
+ ) +} + +export async function getServerSideProps({ locale }) { + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + }, + } +} diff --git a/src/pages/onedrive-vercel-index-oauth/step-2.tsx b/src/pages/onedrive-vercel-index-oauth/step-2.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f4f912ce2f4ec031b2fb44b3cf15708d3863eef7 --- /dev/null +++ b/src/pages/onedrive-vercel-index-oauth/step-2.tsx @@ -0,0 +1,150 @@ +import Head from 'next/head' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation, Trans } from 'next-i18next' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import siteConfig from '../../../config/site.config' +import Navbar from '../../components/Navbar' +import Footer from '../../components/Footer' +import { LoadingIcon } from '../../components/Loading' +import { extractAuthCodeFromRedirected, generateAuthorisationUrl } from '../../utils/oAuthHandler' + +export default function OAuthStep2() { + const router = useRouter() + + const [oAuthRedirectedUrl, setOAuthRedirectedUrl] = useState('') + const [authCode, setAuthCode] = useState('') + const [buttonLoading, setButtonLoading] = useState(false) + + const { t } = useTranslation() + + const oAuthUrl = generateAuthorisationUrl() + + return ( +
+ + {t('OAuth Step 2 - {{title}}', { title: siteConfig.title })} + + +
+ + +
+
+
+ fabulous come back later +
+

+ {t('Welcome to your new onedrive-vercel-index 🎉')} +

+ +

{t('Step 2/3: Get authorisation code')}

+ +

+ + If you are not the owner of this website, + stop now, as continuing with this process may expose your personal files in OneDrive. + +

+ +
{ + window.open(oAuthUrl) + }} + > +
+ +
+
+                {oAuthUrl}
+              
+
+ +

+ + The OAuth link for getting the authorisation code has been created. Click on the link above to get the{' '} + authorisation code. Your browser will + {/* eslint-disable-next-line react/no-unescaped-entities */} + open a new tab to Microsoft's account login page. After logging in and authenticating with your + Microsoft account, you will be redirected to a blank page on localhost. Paste{' '} + the entire redirected URL down below. + +

+ +
+ step 2 screenshot +
+ + { + setOAuthRedirectedUrl(e.target.value) + setAuthCode(extractAuthCodeFromRedirected(e.target.value)) + }} + /> + +

{t('The authorisation code extracted is:')}

+

+ {authCode ?? {t('Waiting for code...')}} +

+ +

+ {authCode + ? t('✅ You can now proceed onto the next step: requesting your access token and refresh token.') + : t('❌ No valid code extracted.')} +

+ +
+ +
+
+
+
+ +
+
+ ) +} + +export async function getServerSideProps({ locale }) { + return { + props: { + ...(await serverSideTranslations(locale, ['common'])), + }, + } +} diff --git a/src/pages/onedrive-vercel-index-oauth/step-3.tsx b/src/pages/onedrive-vercel-index-oauth/step-3.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b21af3a4637c25cf0c7577410a3e68034e29201f --- /dev/null +++ b/src/pages/onedrive-vercel-index-oauth/step-3.tsx @@ -0,0 +1,265 @@ +import Head from 'next/head' +import Image from 'next/image' +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useTranslation, Trans } from 'next-i18next' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import siteConfig from '../../../config/site.config' +import Navbar from '../../components/Navbar' +import Footer from '../../components/Footer' + +import { getAuthPersonInfo, requestTokenWithAuthCode, sendTokenToServer } from '../../utils/oAuthHandler' +import { LoadingIcon } from '../../components/Loading' + +export default function OAuthStep3({ accessToken, expiryTime, refreshToken, error, description, errorUri }) { + const router = useRouter() + const [expiryTimeLeft, setExpiryTimeLeft] = useState(expiryTime) + + const { t } = useTranslation() + + useEffect(() => { + if (!expiryTimeLeft) return + + const intervalId = setInterval(() => { + setExpiryTimeLeft(expiryTimeLeft - 1) + }, 1000) + + return () => clearInterval(intervalId) + }, [expiryTimeLeft]) + + const [buttonContent, setButtonContent] = useState( +
+ {t('Store tokens')} +
+ ) + const [buttonError, setButtonError] = useState(false) + + const sendAuthTokensToServer = async () => { + setButtonError(false) + setButtonContent( +
+ {t('Storing tokens')} +
+ ) + + // verify identity of the authenticated user with the Microsoft Graph API + const { data, status } = await getAuthPersonInfo(accessToken) + if (status !== 200) { + setButtonError(true) + setButtonContent( +
+ {t('Error validating identify, restart')} +
+ ) + return + } + if (data.userPrincipalName !== siteConfig.userPrincipalName) { + setButtonError(true) + setButtonContent( +
+ {t('Do not pretend to be the site owner')} +
+ ) + return + } + + await sendTokenToServer(accessToken, refreshToken, expiryTime) + .then(() => { + setButtonError(false) + setButtonContent( +
+ {t('Stored! Going home...')} +
+ ) + setTimeout(() => { + router.push('/') + }, 2000) + }) + .catch(_ => { + setButtonError(true) + setButtonContent( +
+ {t('Error storing the token')} +
+ ) + }) + } + + return ( +
+ + {t('OAuth Step 3 - {{title}}', { title: siteConfig.title })} + + +
+ + +
+
+
+ fabulous celebration +
+

+ {t('Welcome to your new onedrive-vercel-index 🎉')} +

+ +

{t('Step 3/3: Get access and refresh tokens')}

+ {error ? ( +
+

+ + + {t('Whoops, looks like we got a problem: {{error}}.', { + // t('No auth code present') + error: t(error), + })} + +

+

+ { + // t('Where is the auth code? Did you follow step 2 you silly donut?') + t(description) + } +

+ {errorUri && ( +

+ + Check out{' '} + + {/* eslint-disable-next-line react/no-unescaped-entities */} + Microsoft's official explanation + {' '} + on the error message. + +

+ )} +
+ +
+
+ ) : ( +
+

{t('Success! The API returned what we needed.')}

+
    + {accessToken && ( +
  1. + {' '} + + {t('Acquired access_token: ')} + {`${accessToken.substring(0, 60)}...`} + +
  2. + )} + {refreshToken && ( +
  3. + {' '} + + {t('Acquired refresh_token: ')} + {`${refreshToken.substring(0, 60)}...`} + +
  4. + )} +
+ +

+ {' '} + {t('These tokens may take a few seconds to populate after you click the button below. ') + + t('If you go back home and still see the welcome page telling you to re-authenticate, ') + + t('revisit home and do a hard refresh.')} +

+

+ {t( + 'Final step, click the button below to store these tokens persistently before they expire after {{minutes}} minutes {{seconds}} seconds. ', + { + minutes: Math.floor(expiryTimeLeft / 60), + seconds: expiryTimeLeft - Math.floor(expiryTimeLeft / 60) * 60, + } + ) + + t( + "Don't worry, after storing them, onedrive-vercel-index will take care of token refreshes and updates after your site goes live." + )} +

+ +
+ +
+
+ )} +
+
+
+ +
+
+ ) +} + +export async function getServerSideProps({ query, locale }) { + const { authCode } = query + + // Return if no auth code is present + if (!authCode) { + return { + props: { + error: 'No auth code present', + description: 'Where is the auth code? Did you follow step 2 you silly donut?', + ...(await serverSideTranslations(locale, ['common'])), + }, + } + } + + const response = await requestTokenWithAuthCode(authCode) + + // If error response, return invalid + if ('error' in response) { + return { + props: { + error: response.error, + description: response.errorDescription, + errorUri: response.errorUri, + ...(await serverSideTranslations(locale, ['common'])), + }, + } + } + + const { expiryTime, accessToken, refreshToken } = response + + return { + props: { + error: null, + expiryTime, + accessToken, + refreshToken, + ...(await serverSideTranslations(locale, ['common'])), + }, + } +} diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..8b73a857227744358498f31f63b46a1616c88e83 --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1,30 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer utilities { + /* Chrome, Safari and Opera */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + + .no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } +} + +.react-pdf__Page__canvas { + @apply mx-auto border border-gray-300/40 shadow; +} + +.markdown-body ul { + @apply list-disc; +} +.markdown-body ol { + @apply list-decimal; +} +pre[class*='language-'], +code[class*='language-'] { + @apply font-mono !important; +} diff --git a/src/styles/markdown-github.css b/src/styles/markdown-github.css new file mode 100644 index 0000000000000000000000000000000000000000..b2687a953d93b2630663678a313db0e60dac521c --- /dev/null +++ b/src/styles/markdown-github.css @@ -0,0 +1,1163 @@ +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body .anchor { + float: left; + line-height: 1; + margin-left: -20px; + padding-right: 4px; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: #1b1f23; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body h1:hover .anchor .octicon-link:before, +.markdown-body h2:hover .anchor .octicon-link:before, +.markdown-body h3:hover .anchor .octicon-link:before, +.markdown-body h4:hover .anchor .octicon-link:before, +.markdown-body h5:hover .anchor .octicon-link:before, +.markdown-body h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E"); +} +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + line-height: 1.5; + color: #24292e; + background-color: #ffffff; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body details { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body a { + background-color: initial; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; + font-weight: bolder; +} + +.markdown-body h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre { + font-family: monospace, monospace; + font-size: 1em; +} + +.markdown-body hr { + box-sizing: initial; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type='checkbox'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: rgba(2, 132, 199); + text-decoration: none; +} + +.markdown-body a:hover { + color: rgba(3, 105, 161); + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; +} + +.markdown-body hr:after, +.markdown-body hr:before { + display: table; + content: ''; +} + +.markdown-body hr:after { + clear: both; +} + +.markdown-body table { + border-spacing: 0; + border-collapse: collapse; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body h1 { + font-size: 32px; +} + +.markdown-body h1, +.markdown-body h2 { + font-weight: 600; +} + +.markdown-body h2 { + font-size: 24px; +} + +.markdown-body h3 { + font-size: 20px; +} + +.markdown-body h3, +.markdown-body h4 { + font-weight: 600; +} + +.markdown-body h4 { + font-size: 16px; +} + +.markdown-body h5 { + font-size: 14px; +} + +.markdown-body h5, +.markdown-body h6 { + font-weight: 600; +} + +.markdown-body h6 { + font-size: 12px; +} + +.markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ol, +.markdown-body ul { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ol ol ol, +.markdown-body ol ul ol, +.markdown-body ul ol ol, +.markdown-body ul ul ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code, +.markdown-body pre { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body input::-webkit-inner-spin-button, +.markdown-body input::-webkit-outer-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-body :checked + .radio-label { + position: relative; + z-index: 1; + border-color: #0366d6; +} + +.markdown-body .border { + border: 1px solid #e1e4e8 !important; +} + +.markdown-body .border-0 { + border: 0 !important; +} + +.markdown-body .border-bottom { + border-bottom: 1px solid #e1e4e8 !important; +} + +.markdown-body .rounded-1 { + border-radius: 3px !important; +} + +.markdown-body .bg-white { + background-color: #fff !important; +} + +.markdown-body .bg-gray-light { + background-color: #fafbfc !important; +} + +.markdown-body .text-gray-light { + color: #6a737d !important; +} + +.markdown-body .mb-0 { + margin-bottom: 0 !important; +} + +.markdown-body .my-2 { + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .py-2 { + padding-top: 8px !important; + padding-bottom: 8px !important; +} + +.markdown-body .pl-3, +.markdown-body .px-3 { + padding-left: 16px !important; +} + +.markdown-body .px-3 { + padding-right: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .f6 { + font-size: 12px !important; +} + +.markdown-body .lh-condensed { + line-height: 1.25 !important; +} + +.markdown-body .text-bold { + font-weight: 600 !important; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-s .pl-s1, +.markdown-body .pl-smi { + color: #24292e; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-pds, +.markdown-body .pl-s, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sra, +.markdown-body .pl-sr .pl-sre { + color: #032f62; +} + +.markdown-body .pl-smw, +.markdown-body .pl-v { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + color: #fafbfc; + background-color: #b31d28; +} + +.markdown-body .pl-c2 { + color: #fafbfc; + background-color: #d73a49; +} + +.markdown-body .pl-c2:before { + content: '^M'; +} + +.markdown-body .pl-sr .pl-cce { + font-weight: 700; + color: #22863a; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + font-weight: 700; + color: #005cc5; +} + +.markdown-body .pl-mi { + font-style: italic; + color: #24292e; +} + +.markdown-body .pl-mb { + font-weight: 700; + color: #24292e; +} + +.markdown-body .pl-md { + color: #b31d28; + background-color: #ffeef0; +} + +.markdown-body .pl-mi1 { + color: #22863a; + background-color: #f0fff4; +} + +.markdown-body .pl-mc { + color: #e36209; + background-color: #ffebda; +} + +.markdown-body .pl-mi2 { + color: #f6f8fa; + background-color: #005cc5; +} + +.markdown-body .pl-mdr { + font-weight: 700; + color: #6f42c1; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + text-decoration: underline; + color: #032f62; +} + +.markdown-body .mb-0 { + margin-bottom: 0 !important; +} + +.markdown-body .my-2 { + margin-bottom: 8px !important; +} + +.markdown-body .my-2 { + margin-top: 8px !important; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .py-2 { + padding-top: 8px !important; + padding-bottom: 8px !important; +} + +.markdown-body .pl-3 { + padding-left: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .pl-7 { + padding-left: 48px !important; +} + +.markdown-body .pl-8 { + padding-left: 64px !important; +} + +.markdown-body .pl-9 { + padding-left: 80px !important; +} + +.markdown-body .pl-10 { + padding-left: 96px !important; +} + +.markdown-body .pl-11 { + padding-left: 112px !important; +} + +.markdown-body .pl-12 { + padding-left: 128px !important; +} + +.markdown-body hr { + border-bottom-color: #eee; +} + +.markdown-body kbd { + display: inline-block; + padding: 3px 5px; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; +} + +.markdown-body:after, +.markdown-body:before { + display: table; + content: ''; +} + +.markdown-body:after { + clear: both; +} + +.markdown-body > :first-child { + margin-top: 0 !important; +} + +.markdown-body > :last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body blockquote, +.markdown-body details, +.markdown-body dl, +.markdown-body ol, +.markdown-body p, +.markdown-body pre, +.markdown-body table, +.markdown-body ul { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-body hr { + height: 0.25em; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; +} + +.markdown-body blockquote { + padding: 0 1em; + color: #6a737d; + border-left: 0.25em solid #dfe2e5; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; +} + +.markdown-body h1 { + font-size: 2em; +} + +.markdown-body h1, +.markdown-body h2 { + padding-bottom: 0.3em; + border-bottom: 1px solid #eaecef; +} + +.markdown-body h2 { + font-size: 1.5em; +} + +.markdown-body h3 { + font-size: 1.25em; +} + +.markdown-body h4 { + font-size: 1em; +} + +.markdown-body h5 { + font-size: 0.875em; +} + +.markdown-body h6 { + font-size: 0.85em; + color: #6a737d; +} + +.markdown-body ol, +.markdown-body ul { + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ol ul, +.markdown-body ul ol, +.markdown-body ul ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: 600; +} + +.markdown-body dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-body table { + display: block; + width: 100%; + overflow: auto; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table td, +.markdown-body table th { + padding: 6px 13px; + border: 1px solid #dfe2e5; +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body img { + max-width: 100%; + box-sizing: initial; + background-color: #fff; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre > div > code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + color: #d4d4d4; + border: 0; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #1e1e1e; + border-radius: 3px; +} + +.markdown-body pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: initial; + border: 0; +} + +.markdown-body .commit-tease-sha { + display: inline-block; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 90%; + color: #444d56; +} + +.markdown-body .full-commit .btn-outline:not(:disabled):hover { + color: #005cc5; + border-color: #005cc5; +} + +.markdown-body .blob-wrapper { + overflow-x: auto; + overflow-y: hidden; +} + +.markdown-body .blob-wrapper-embedded { + max-height: 240px; + overflow-y: auto; +} + +.markdown-body .blob-num { + width: 1%; + min-width: 50px; + padding-right: 10px; + padding-left: 10px; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 12px; + line-height: 20px; + color: rgba(27, 31, 35, 0.3); + text-align: right; + white-space: nowrap; + vertical-align: top; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.markdown-body .blob-num:hover { + color: rgba(27, 31, 35, 0.6); +} + +.markdown-body .blob-num:before { + content: attr(data-line-number); +} + +.markdown-body .blob-code { + position: relative; + padding-right: 10px; + padding-left: 10px; + line-height: 20px; + vertical-align: top; +} + +.markdown-body .blob-code-inner { + overflow: visible; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 12px; + color: #24292e; + word-wrap: normal; + white-space: pre; +} + +.markdown-body .pl-token.active, +.markdown-body .pl-token:hover { + cursor: pointer; + background: #ffea7f; +} + +.markdown-body .tab-size[data-tab-size='1'] { + -moz-tab-size: 1; + tab-size: 1; +} + +.markdown-body .tab-size[data-tab-size='2'] { + -moz-tab-size: 2; + tab-size: 2; +} + +.markdown-body .tab-size[data-tab-size='3'] { + -moz-tab-size: 3; + tab-size: 3; +} + +.markdown-body .tab-size[data-tab-size='4'] { + -moz-tab-size: 4; + tab-size: 4; +} + +.markdown-body .tab-size[data-tab-size='5'] { + -moz-tab-size: 5; + tab-size: 5; +} + +.markdown-body .tab-size[data-tab-size='6'] { + -moz-tab-size: 6; + tab-size: 6; +} + +.markdown-body .tab-size[data-tab-size='7'] { + -moz-tab-size: 7; + tab-size: 7; +} + +.markdown-body .tab-size[data-tab-size='8'] { + -moz-tab-size: 8; + tab-size: 8; +} + +.markdown-body .tab-size[data-tab-size='9'] { + -moz-tab-size: 9; + tab-size: 9; +} + +.markdown-body .tab-size[data-tab-size='10'] { + -moz-tab-size: 10; + tab-size: 10; +} + +.markdown-body .tab-size[data-tab-size='11'] { + -moz-tab-size: 11; + tab-size: 11; +} + +.markdown-body .tab-size[data-tab-size='12'] { + -moz-tab-size: 12; + tab-size: 12; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} + +@media (prefers-color-scheme: dark) { + .markdown-body { + /* color: #c9d1d9; */ + /* background-color: #0d1117; */ + color: #f3f4f6; + background-color: #18181b; + } + .markdown-body a { + color: rgba(2, 132, 199); + } + .markdown-body hr { + border-bottom: 1px solid #21262d; + border-bottom-color: #21262d; + background-color: #30363d; + } + .markdown-body kbd { + color: #b1bac4; + background-color: #0d1117; + border: 1px solid #6e7681; + box-shadow: inset 0 -1px 0 #6e7681; + } + .markdown-body :checked + .radio-label { + border-color: rgba(56, 139, 253, 0.4); + } + .markdown-body .border { + border: 1px solid #30363d !important; + } + .markdown-body .border-bottom { + border-bottom: 1px solid #30363d !important; + } + .markdown-body .bg-white { + background-color: #0d1117 !important; + } + .markdown-body .bg-gray-light { + background-color: #0d1117 !important; + } + .markdown-body .text-gray-light { + color: #8b949e !important; + } + .markdown-body .pl-c { + color: #8b949e; + } + .markdown-body .pl-c1, + .markdown-body .pl-s .pl-v { + color: #79c0ff; + } + .markdown-body .pl-e, + .markdown-body .pl-en { + color: #d2a8ff; + } + .markdown-body .pl-s .pl-s1, + .markdown-body .pl-smi { + color: #c9d1d9; + } + .markdown-body .pl-ent { + color: #7ee787; + } + .markdown-body .pl-k { + color: #ff7b72; + } + .markdown-body .pl-pds, + .markdown-body .pl-s, + .markdown-body .pl-s .pl-pse .pl-s1, + .markdown-body .pl-sr, + .markdown-body .pl-sr .pl-cce, + .markdown-body .pl-sr .pl-sra, + .markdown-body .pl-sr .pl-sre { + color: #a5d6ff; + } + .markdown-body .pl-smw, + .markdown-body .pl-v { + color: #ffa657; + } + .markdown-body .pl-bu { + color: #f85149; + } + .markdown-body .pl-ii { + color: #f0f6fc; + background-color: #8e1519; + } + .markdown-body .pl-c2 { + color: #f0f6fc; + background-color: #b62324; + } + .markdown-body .pl-sr .pl-cce { + color: #7ee787; + } + .markdown-body .pl-ml { + color: #f2cc60; + } + .markdown-body .pl-mh, + .markdown-body .pl-mh .pl-en, + .markdown-body .pl-ms { + color: #1f6feb; + } + .markdown-body .pl-mi { + color: #c9d1d9; + } + .markdown-body .pl-mb { + color: #c9d1d9; + } + .markdown-body .pl-md { + color: #ffdcd7; + background-color: #67060c; + } + .markdown-body .pl-mi1 { + color: #aff5b4; + background-color: #033a16; + } + .markdown-body .pl-mc { + color: #ffdfb6; + background-color: #5a1e02; + } + .markdown-body .pl-mi2 { + color: #c9d1d9; + background-color: #1158c7; + } + .markdown-body .pl-mdr { + color: #d2a8ff; + } + .markdown-body .pl-ba { + color: #8b949e; + } + .markdown-body .pl-sg { + color: #484f58; + } + .markdown-body .pl-corl { + color: #a5d6ff; + } + .markdown-body blockquote { + color: #8b949e; + border-left: 0.25em solid #3b434b; + } + .markdown-body h1, + .markdown-body h2 { + border-bottom: 1px solid #21262d; + } + .markdown-body h6 { + color: #8b949e; + } + .markdown-body table td, + .markdown-body table th { + border: 1px solid #3b434b; + } + .markdown-body table tr { + background-color: #0d1117; + border-top: 1px solid #272c32; + } + .markdown-body table tr:nth-child(2n) { + background-color: #161b22; + } + .markdown-body img { + background-color: #0d1117; + } + .markdown-body code { + background-color: rgba(240, 246, 252, 0.15); + } + .markdown-body .highlight pre, + .markdown-body pre { + background-color: #1f1f22; + } + .markdown-body .commit-tease-sha { + color: #b1bac4; + } + .markdown-body .blob-num { + color: rgba(240, 246, 252, 0.3); + } + .markdown-body .blob-num:hover { + color: rgba(240, 246, 252, 0.6); + } + .markdown-body .blob-code-inner { + color: #c9d1d9; + } +} diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1adc0be7771c223d6b28b84250e5c50e5d47a66 --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,72 @@ +// API response object for /api/?path=, this may return either a file or a folder. +// Pagination is also declared here with the 'next' parameter. +export type OdAPIResponse = { file?: OdFileObject; folder?: OdFolderObject; next?: string } +// A folder object returned from the OneDrive API. This contains the parameter 'value', which is an array of items +// inside the folder. The items may also be either files or folders. +export type OdFolderObject = { + '@odata.count': number + '@odata.context': string + '@odata.nextLink'?: string + value: Array<{ + id: string + name: string + size: number + lastModifiedDateTime: string + file?: { mimeType: string; hashes: { quickXorHash?: string; sha1Hash?: string; sha256Hash?: string } } + folder?: { childCount: number; view: { sortBy: string; sortOrder: 'ascending'; viewType: 'thumbnails' } } + image?: OdImageFile + video?: OdVideoFile + }> +} +export type OdFolderChildren = OdFolderObject['value'][number] +// A file object returned from the OneDrive API. This object may contain 'video' if the file is a video. +export type OdFileObject = { + '@odata.context': string + name: string + size: number + id: string + lastModifiedDateTime: string + file: { mimeType: string; hashes: { quickXorHash: string; sha1Hash?: string; sha256Hash?: string } } + image?: OdImageFile + video?: OdVideoFile +} +// A representation of a OneDrive image file. Some images do not return a width and height, so types are optional. +export type OdImageFile = { + width?: number + height?: number +} +// A representation of a OneDrive video file. All fields are declared here, but we mainly use 'width' and 'height'. +export type OdVideoFile = { + width: number + height: number + duration: number + bitrate: number + frameRate: number + audioBitsPerSample: number + audioChannels: number + audioFormat: string + audioSamplesPerSecond: number +} +export type OdThumbnail = { + id: string + large: { height: number; width: number; url: string } + medium: { height: number; width: number; url: string } + small: { height: number; width: number; url: string } +} +// API response object for /api/search/?q=. Likewise, this array of items may also contain either files or folders. +export type OdSearchResult = Array<{ + id: string + name: string + file?: OdFileObject + folder?: OdFolderObject + path: string + parentReference: { id: string; name: string; path: string } +}> +// API response object for /api/item/?id={id}. This is primarily used for determining the path of the driveItem by ID. +export type OdDriveItem = { + '@odata.context': string + '@odata.etag': string + id: string + name: string + parentReference: { driveId: string; driveType: string; id: string; path: string } +} diff --git a/src/utils/fetchOnMount.ts b/src/utils/fetchOnMount.ts new file mode 100644 index 0000000000000000000000000000000000000000..818db8e50b1741271e3a3692e92904503e084afe --- /dev/null +++ b/src/utils/fetchOnMount.ts @@ -0,0 +1,31 @@ +import axios from 'axios' +import { useEffect, useState } from 'react' +import { getStoredToken } from './protectedRouteHandler' + +/** + * Custom hook for axios to fetch raw file content on component mount + * @param fetchUrl The URL pointing to the raw file content + * @param path The path of the file, used for determining whether path is protected + */ +export default function useFileContent( + fetchUrl: string, + path: string +): { response: any; error: string; validating: boolean } { + const [response, setResponse] = useState('') + const [validating, setValidating] = useState(true) + const [error, setError] = useState('') + + useEffect(() => { + const hashedToken = getStoredToken(path) + const url = fetchUrl + (hashedToken ? `&odpt=${hashedToken}` : '') + + axios + // Using 'blob' as response type to get the response as a raw file blob, which is later parsed as a string. + // Axios defaults response parsing to JSON, which causes issues when parsing JSON files. + .get(url, { responseType: 'blob' }) + .then(async res => setResponse(await res.data.text())) + .catch(e => setError(e.message)) + .finally(() => setValidating(false)) + }, [fetchUrl, path]) + return { response, error, validating } +} diff --git a/src/utils/fetchWithSWR.ts b/src/utils/fetchWithSWR.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc57b54102837cd8391b7ffe2471d2f920d8fd60 --- /dev/null +++ b/src/utils/fetchWithSWR.ts @@ -0,0 +1,57 @@ +import axios from 'axios' +import useSWRInfinite from 'swr/infinite' + +import type { OdAPIResponse } from '../types' + +import { getStoredToken } from './protectedRouteHandler' + +// Common axios fetch function for use with useSWR +export async function fetcher([url, token]: [url: string, token?: string]): Promise { + try { + return ( + await (token + ? axios.get(url, { + headers: { 'od-protected-token': token }, + }) + : axios.get(url)) + ).data + } catch (err: any) { + throw { status: err.response.status, message: err.response.data } + } +} + +/** + * Paging with useSWRInfinite + protected token support + * @param path Current query directory path + * @returns useSWRInfinite API + */ +export function useProtectedSWRInfinite(path: string = '') { + const hashedToken = getStoredToken(path) + + /** + * Next page infinite loading for useSWR + * @param pageIdx The index of this paging collection + * @param prevPageData Previous page information + * @param path Directory path + * @returns API to the next page + */ + function getNextKey(pageIndex: number, previousPageData: OdAPIResponse): (string | null)[] | null { + // Reached the end of the collection + if (previousPageData && !previousPageData.folder) return null + + // First page with no prevPageData + if (pageIndex === 0) return [`/api/?path=${path}`, hashedToken] + + // Add nextPage token to API endpoint + return [`/api/?path=${path}&next=${previousPageData.next}`, hashedToken] + } + + // Disable auto-revalidate, these options are equivalent to useSWRImmutable + // https://swr.vercel.app/docs/revalidation#disable-automatic-revalidations + const revalidationOptions = { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + return useSWRInfinite(getNextKey, fetcher, revalidationOptions) +} diff --git a/src/utils/fileDetails.ts b/src/utils/fileDetails.ts new file mode 100644 index 0000000000000000000000000000000000000000..868217d0135f5c365d26fc76793206c1421af837 --- /dev/null +++ b/src/utils/fileDetails.ts @@ -0,0 +1,28 @@ +import dayjs from 'dayjs' + +import siteConfig from '../../config/site.config' + +/** + * Convert raw bits file/folder size into a human readable string + * + * @param size File or folder size, in raw bits + * @returns Human readable form of the file or folder size + */ +export const humanFileSize = (size: number) => { + if (size < 1024) return size + ' B' + const i = Math.floor(Math.log(size) / Math.log(1024)) + const num = size / Math.pow(1024, i) + const round = Math.round(num) + const formatted = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round + return `${formatted} ${'KMGTPEZY'[i - 1]}B` +} + +/** + * Convert the last modified date time into locale friendly string + * + * @param lastModifedDateTime DateTime string in ISO format + * @returns Human readable form of the file or folder last modified date + */ +export const formatModifiedDateTime = (lastModifedDateTime: string) => { + return dayjs(lastModifedDateTime).format(siteConfig.datetimeFormat) +} diff --git a/src/utils/getBaseUrl.ts b/src/utils/getBaseUrl.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0de661c7440a1af7e93a19b1fb24576c874e101 --- /dev/null +++ b/src/utils/getBaseUrl.ts @@ -0,0 +1,10 @@ +/** + * Extract the current web page's base url + * @returns base url of the page + */ +export function getBaseUrl(): string { + if (typeof window !== 'undefined') { + return window.location.origin + } + return '' +} diff --git a/src/utils/getFileIcon.ts b/src/utils/getFileIcon.ts new file mode 100644 index 0000000000000000000000000000000000000000..8240c6def084a1670df8cd646f59719864ebd7a3 --- /dev/null +++ b/src/utils/getFileIcon.ts @@ -0,0 +1,130 @@ +import type { IconPrefix, IconName } from '@fortawesome/fontawesome-svg-core' + +const icons: { [key: string]: [IconPrefix, IconName] } = { + image: ['far', 'file-image'], + pdf: ['far', 'file-pdf'], + word: ['far', 'file-word'], + powerpoint: ['far', 'file-powerpoint'], + excel: ['far', 'file-excel'], + audio: ['far', 'file-audio'], + video: ['far', 'file-video'], + archive: ['far', 'file-archive'], + code: ['far', 'file-code'], + text: ['far', 'file-alt'], + file: ['far', 'file'], + markdown: ['fab', 'markdown'], + book: ['fas', 'book'], + link: ['fas', 'link'], +} + +const extensions = { + gif: icons.image, + jpeg: icons.image, + jpg: icons.image, + png: icons.image, + heic: icons.image, + webp: icons.image, + + pdf: icons.pdf, + + doc: icons.word, + docx: icons.word, + + ppt: icons.powerpoint, + pptx: icons.powerpoint, + + xls: icons.excel, + xlsx: icons.excel, + + aac: icons.audio, + mp3: icons.audio, + ogg: icons.audio, + flac: icons.audio, + oga: icons.audio, + opus: icons.audio, + m4a: icons.audio, + + avi: icons.video, + flv: icons.video, + mkv: icons.video, + mp4: icons.video, + + '7z': icons.archive, + bz2: icons.archive, + xz: icons.archive, + wim: icons.archive, + gz: icons.archive, + rar: icons.archive, + tar: icons.archive, + zip: icons.archive, + + c: icons.code, + cpp: icons.code, + js: icons.code, + jsx: icons.code, + java: icons.code, + sh: icons.code, + cs: icons.code, + py: icons.code, + css: icons.code, + html: icons.code, + ts: icons.code, + tsx: icons.code, + rs: icons.code, + vue: icons.code, + json: icons.code, + yml: icons.code, + yaml: icons.code, + toml: icons.code, + + txt: icons.text, + rtf: icons.text, + vtt: icons.text, + srt: icons.text, + log: icons.text, + diff: icons.text, + + md: icons.markdown, + + epub: icons.book, + mobi: icons.book, + azw3: icons.book, + + url: icons.link, +} + +/** + * To stop TypeScript complaining about indexing the object with a non-existent key + * https://dev.to/mapleleaf/indexing-objects-in-typescript-1cgi + * + * Fixed by ChatGPT with the upgrade of TypeScript 4.9 + * + * @param obj Object with keys to index + * @param key The index key + * @returns Whether or not the key exists inside the object + */ +export function hasKey(obj: Record, key: string): boolean { + return key in obj +} + +export function getRawExtension(fileName: string): string { + return fileName.slice(((fileName.lastIndexOf('.') - 1) >>> 0) + 2) +} +export function getExtension(fileName: string): string { + return getRawExtension(fileName).toLowerCase() +} + +export function getFileIcon(fileName: string, flags?: { video?: boolean }): [IconPrefix, IconName] { + const extension = getExtension(fileName) + let icon = hasKey(extensions, extension) ? extensions[extension] : icons.file + + // Files with '.ts' extensions may be TypeScript files or TS Video files, we check for the flag 'video' + // to determine which icon to render for '.ts' files. + if (extension === 'ts') { + if (flags?.video) { + icon = icons.video + } + } + + return icon +} diff --git a/src/utils/getPreviewType.ts b/src/utils/getPreviewType.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c180c45e9eb450e9f740d9c8317222429916276 --- /dev/null +++ b/src/utils/getPreviewType.ts @@ -0,0 +1,123 @@ +import { getExtension } from './getFileIcon' + +export const preview = { + markdown: 'markdown', + image: 'image', + text: 'text', + pdf: 'pdf', + code: 'code', + video: 'video', + audio: 'audio', + office: 'ms-office', + epub: 'epub', + url: 'url', +} + +export const extensions = { + gif: preview.image, + jpeg: preview.image, + jpg: preview.image, + png: preview.image, + webp: preview.image, + + md: preview.markdown, + markdown: preview.markdown, + mdown: preview.markdown, + + pdf: preview.pdf, + + doc: preview.office, + docx: preview.office, + ppt: preview.office, + pptx: preview.office, + xls: preview.office, + xlsx: preview.office, + + c: preview.code, + cpp: preview.code, + js: preview.code, + jsx: preview.code, + java: preview.code, + sh: preview.code, + cs: preview.code, + py: preview.code, + css: preview.code, + html: preview.code, + // typescript or video file, determined below + ts: preview.code, + tsx: preview.code, + rs: preview.code, + vue: preview.code, + json: preview.code, + yml: preview.code, + yaml: preview.code, + toml: preview.code, + + txt: preview.text, + vtt: preview.text, + srt: preview.text, + log: preview.text, + diff: preview.text, + + mp4: preview.video, + flv: preview.video, + webm: preview.video, + m3u8: preview.video, + mkv: preview.video, + mov: preview.video, + avi: preview.video, // won't work! + + mp3: preview.audio, + m4a: preview.audio, + aac: preview.audio, + wav: preview.audio, + ogg: preview.audio, + oga: preview.audio, + opus: preview.audio, + flac: preview.audio, + + epub: preview.epub, + + url: preview.url, +} + +export function getPreviewType(extension: string, flags?: { video?: boolean }): string | undefined { + let previewType = extensions[extension] + if (!previewType) { + return previewType + } + + // Files with '.ts' extensions may be TypeScript files or TS Video files, we check for the flag 'video' + // to determine what preview renderer to use for '.ts' files. + if (extension === 'ts') { + if (flags?.video) { + previewType = preview.video + } + } + + return previewType +} + +export function getLanguageByFileName(filename: string): string { + const extension = getExtension(filename) + switch (extension) { + case 'ts': + case 'tsx': + return 'typescript' + case 'rs': + return 'rust' + case 'js': + case 'jsx': + return 'javascript' + case 'sh': + return 'shell' + case 'cs': + return 'csharp' + case 'py': + return 'python' + case 'yml': + return 'yaml' + default: + return extension + } +} diff --git a/src/utils/getReadablePath.ts b/src/utils/getReadablePath.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a3fe88fa24a435322032ae3180acedbde759aee --- /dev/null +++ b/src/utils/getReadablePath.ts @@ -0,0 +1,38 @@ +/** + * Make path readable but still valid in URL (means the whole URL is still recognized as a URL) + * @param path Path. May be used as URL path or query value. + * @returns Readable but still valid path + */ +export function getReadablePath(path: string) { + path = path + .split('/') + .map(s => decodeURIComponent(s)) + .map(s => + Array.from(s) + .map(c => (isSafeChar(c) ? c : encodeURIComponent(c))) + .join('') + ) + .join('/') + return path +} + +// Check if the character is safe (means no need of percent-encoding) +function isSafeChar(c: string) { + if (c.charCodeAt(0) < 0x80) { + // ASCII + if (/^[a-zA-Z0-9\-._~]$/.test(c)) { + // RFC3986 unreserved chars + return true + } else if (/^[*:@,!]$/.test(c)) { + // Some extra pretty safe chars for URL path or query + // Ref: https://stackoverflow.com/a/42287988/11691878 + return true + } + } else { + if (!/\s|\u180e/.test(c)) { + // Non-whitespace char. \u180e is missed in \s. + return true + } + } + return false +} diff --git a/src/utils/oAuthHandler.ts b/src/utils/oAuthHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fde3d5d3dd25d63be23c994f1cc5c952748fa67 --- /dev/null +++ b/src/utils/oAuthHandler.ts @@ -0,0 +1,111 @@ +import axios from 'axios' +import CryptoJS from 'crypto-js' + +import apiConfig from '../../config/api.config' + +// Just a disguise to obfuscate required tokens (including but not limited to client secret, +// access tokens, and refresh tokens), used along with the following two functions +const AES_SECRET_KEY = 'onedrive-vercel-index' +export function obfuscateToken(token: string): string { + // Encrypt token with AES + const encrypted = CryptoJS.AES.encrypt(token, AES_SECRET_KEY) + return encrypted.toString() +} +export function revealObfuscatedToken(obfuscated: string): string { + // Decrypt SHA256 obfuscated token + const decrypted = CryptoJS.AES.decrypt(obfuscated, AES_SECRET_KEY) + return decrypted.toString(CryptoJS.enc.Utf8) +} + +// Generate the Microsoft OAuth 2.0 authorization URL, used for requesting the authorisation code +export function generateAuthorisationUrl(): string { + const { clientId, redirectUri, authApi, scope } = apiConfig + const authUrl = authApi.replace('/token', '/authorize') + + // Construct URL parameters for OAuth2 + const params = new URLSearchParams() + params.append('client_id', clientId) + params.append('redirect_uri', redirectUri) + params.append('response_type', 'code') + params.append('scope', scope) + params.append('response_mode', 'query') + + return `${authUrl}?${params.toString()}` +} + +// The code returned from the Microsoft OAuth 2.0 authorization URL is a request URL with hostname +// http://localhost and URL parameter code. This function extracts the code from the request URL +export function extractAuthCodeFromRedirected(url: string): string { + // Return empty string if the url is not the defined redirect uri + if (!url.startsWith(apiConfig.redirectUri)) { + return '' + } + + // New URL search parameter + const params = new URLSearchParams(url.split('?')[1]) + return params.get('code') ?? '' +} + +// After a successful authorisation, the code returned from the Microsoft OAuth 2.0 authorization URL +// will be used to request an access token. This function requests the access token with the authorisation code +// and returns the access token and refresh token on success. +export async function requestTokenWithAuthCode( + code: string +): Promise< + | { expiryTime: string; accessToken: string; refreshToken: string } + | { error: string; errorDescription: string; errorUri: string } +> { + const { clientId, redirectUri, authApi } = apiConfig + const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret) + + // Construct URL parameters for OAuth2 + const params = new URLSearchParams() + params.append('client_id', clientId) + params.append('redirect_uri', redirectUri) + params.append('client_secret', clientSecret) + params.append('code', code) + params.append('grant_type', 'authorization_code') + + // Request access token + return axios + .post(authApi, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }) + .then(resp => { + const { expires_in, access_token, refresh_token } = resp.data + return { expiryTime: expires_in, accessToken: access_token, refreshToken: refresh_token } + }) + .catch(err => { + const { error, error_description, error_uri } = err.response.data + return { error, errorDescription: error_description, errorUri: error_uri } + }) +} + +// Verify the identity of the user with the access token and compare it with the userPrincipalName +// in the Microsoft Graph API. If the userPrincipalName matches, proceed with token storing. +export async function getAuthPersonInfo(accessToken: string) { + const profileApi = apiConfig.driveApi.replace('/drive', '') + return axios.get(profileApi, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) +} + +export async function sendTokenToServer(accessToken: string, refreshToken: string, expiryTime: string) { + return await axios.post( + '/api', + { + obfuscatedAccessToken: obfuscateToken(accessToken), + accessTokenExpiry: parseInt(expiryTime), + obfuscatedRefreshToken: obfuscateToken(refreshToken), + }, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ) +} diff --git a/src/utils/odAuthTokenStore.ts b/src/utils/odAuthTokenStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5bb31a56acb6e62f6c443c424ac43be35c8a90d --- /dev/null +++ b/src/utils/odAuthTokenStore.ts @@ -0,0 +1,29 @@ +import Redis from 'ioredis' +import siteConfig from '../../config/site.config' + +// Persistent key-value store is provided by Redis, hosted on Upstash +// https://vercel.com/integrations/upstash +const kv = new Redis(process.env.REDIS_URL || '') + +export async function getOdAuthTokens(): Promise<{ accessToken: unknown; refreshToken: unknown }> { + const accessToken = await kv.get(`${siteConfig.kvPrefix}access_token`) + const refreshToken = await kv.get(`${siteConfig.kvPrefix}refresh_token`) + + return { + accessToken, + refreshToken, + } +} + +export async function storeOdAuthTokens({ + accessToken, + accessTokenExpiry, + refreshToken, +}: { + accessToken: string + accessTokenExpiry: number + refreshToken: string +}): Promise { + await kv.set(`${siteConfig.kvPrefix}access_token`, accessToken, 'EX', accessTokenExpiry) + await kv.set(`${siteConfig.kvPrefix}refresh_token`, refreshToken) +} diff --git a/src/utils/protectedRouteHandler.ts b/src/utils/protectedRouteHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e2bbdb333a09008ab9d7ab4639fed26cc93ac23 --- /dev/null +++ b/src/utils/protectedRouteHandler.ts @@ -0,0 +1,58 @@ +import sha256 from 'crypto-js/sha256' +import siteConfig from '../../config/site.config' + +// Hash password token with SHA256 +function encryptToken(token: string): string { + return sha256(token).toString() +} + +// Fetch stored token from localStorage and encrypt with SHA256 +export function getStoredToken(path: string): string | null { + const storedToken = + typeof window !== 'undefined' ? JSON.parse(localStorage.getItem(matchProtectedRoute(path)) as string) : '' + return storedToken ? encryptToken(storedToken) : null +} + +/** + * Compares the hash of .password and od-protected-token header + * @param odTokenHeader od-protected-token header (sha256 hashed token) + * @param dotPassword non-hashed .password file + * @returns whether the two hashes are the same + */ +export function compareHashedToken({ + odTokenHeader, + dotPassword, +}: { + odTokenHeader: string + dotPassword: string +}): boolean { + return encryptToken(dotPassword.trim()) === odTokenHeader +} +/** + * Match the specified route against a list of predefined routes + * @param route directory path + * @returns whether the directory is protected + */ + +export function matchProtectedRoute(route: string): string { + const protectedRoutes: string[] = siteConfig.protectedRoutes + let authTokenPath = '' + + for (const r of protectedRoutes) { + // protected route array could be empty + if (r) { + if ( + route.startsWith( + r + .split('/') + .map(p => encodeURIComponent(p)) + .join('/') + ) + ) { + authTokenPath = r + break + } + } + } + return authTokenPath +} diff --git a/src/utils/useDeviceOS.ts b/src/utils/useDeviceOS.ts new file mode 100644 index 0000000000000000000000000000000000000000..232e03fb7c036e8cf75d663d46de4a91e413d810 --- /dev/null +++ b/src/utils/useDeviceOS.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react' + +export default function useDeviceOS(): string { + const [os, setOS] = useState('') + + useEffect(() => { + const userAgent = window.navigator.userAgent + + if (userAgent.indexOf('Windows') > -1) { + setOS('windows') + } else if (userAgent.indexOf('Mac OS') > -1) { + setOS('mac') + } else if (userAgent.indexOf('Linux') > -1) { + setOS('linux') + } else { + setOS('other') + } + }, []) + + return os +} diff --git a/src/utils/useLocalStorage.ts b/src/utils/useLocalStorage.ts new file mode 100644 index 0000000000000000000000000000000000000000..b137b2fdf85c6e427e589af784c5aacac992b603 --- /dev/null +++ b/src/utils/useLocalStorage.ts @@ -0,0 +1,78 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react' + +type SetValue = Dispatch> + +function useLocalStorage(key: string, initialValue: T): [T, SetValue] { + // Get from local storage then + // parse stored json or return initialValue + const readValue = (): T => { + // Prevent build error "window is undefined" but keep keep working + if (typeof window === 'undefined') { + return initialValue + } + + try { + const item = window.localStorage.getItem(key) + return item ? (JSON.parse(item) as T) : initialValue + } catch (error) { + console.warn(`Error reading localStorage key “${key}”:`, error) + return initialValue + } + } + + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(readValue) + + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue: SetValue = value => { + // Prevent build error "window is undefined" but keeps working + if (typeof window == 'undefined') { + console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`) + } + + try { + // Allow value to be a function so we have the same API as useState + const newValue = value instanceof Function ? value(storedValue) : value + + // Save to local storage + window.localStorage.setItem(key, JSON.stringify(newValue)) + + // Save state + setStoredValue(newValue) + + // We dispatch a custom event so every useLocalStorage hook are notified + window.dispatchEvent(new Event('local-storage')) + } catch (error) { + console.warn(`Error setting localStorage key “${key}”:`, error) + } + } + + useEffect(() => { + setStoredValue(readValue()) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + const handleStorageChange = () => { + setStoredValue(readValue()) + } + + // this only works for other documents, not the current one + window.addEventListener('storage', handleStorageChange) + + // this is a custom event, triggered in writeValueToLocalStorage + window.addEventListener('local-storage', handleStorageChange) + + return () => { + window.removeEventListener('storage', handleStorageChange) + window.removeEventListener('local-storage', handleStorageChange) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return [storedValue, setValue] +} + +export default useLocalStorage diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000000000000000000000000000000000000..43d782d70c737e87fcc3b3b5f506f4bee25691f7 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,43 @@ +const defaultTheme = require('tailwindcss/defaultTheme') +const colors = require('tailwindcss/colors') +const siteConfig = require('./config/site.config') + +module.exports = { + content: ['./src/**/*.{js,ts,jsx,tsx}'], + theme: { + colors: { + transparent: 'transparent', + current: 'currentColor', + black: colors.black, + white: colors.white, + gray: colors.zinc, + red: colors.rose, + yellow: colors.amber, + green: colors.green, + blue: colors.sky, + indigo: colors.indigo, + purple: colors.purple, + pink: colors.pink, + teal: colors.teal, + cyan: colors.cyan, + orange: colors.orange, + }, + extend: { + fontFamily: { + sans: [`"${siteConfig.googleFontSans}"`, '"Noto Sans SC"', ...defaultTheme.fontFamily.sans], + mono: [`"${siteConfig.googleFontMono}"`, ...defaultTheme.fontFamily.mono] + }, + colors: { + gray: { + 850: '#222226' + } + }, + animation: { + 'spin-slow': 'spin 5s linear infinite', + } + } + }, + plugins: [ + require('@tailwindcss/line-clamp'), + ], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..4c18a0332af14ed1294a7ebab8338c48a6267fd1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "noImplicitAny": false, + "incremental": true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +}